mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-07-22 05:45:04 +00:00
Merge branch 'develop' of github.com:HabitRPG/habitica-android into develop
This commit is contained in:
commit
4790a8dccb
53 changed files with 492 additions and 245 deletions
|
|
@ -47,8 +47,8 @@ dependencies {
|
||||||
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
|
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
|
||||||
|
|
||||||
//Dependency Injection
|
//Dependency Injection
|
||||||
implementation 'com.google.dagger:dagger:2.38'
|
implementation 'com.google.dagger:dagger:2.39.1'
|
||||||
kapt 'com.google.dagger:dagger-compiler:2.38'
|
kapt 'com.google.dagger:dagger-compiler:2.39.1'
|
||||||
compileOnly 'javax.annotation:javax.annotation-api:1.3.2'
|
compileOnly 'javax.annotation:javax.annotation-api:1.3.2'
|
||||||
compileOnly 'com.github.pengrad:jdk9-deps:1.0'
|
compileOnly 'com.github.pengrad:jdk9-deps:1.0'
|
||||||
//App Compatibility and Material Design
|
//App Compatibility and Material Design
|
||||||
|
|
@ -82,8 +82,8 @@ dependencies {
|
||||||
//Analytics
|
//Analytics
|
||||||
implementation 'com.amplitude:android-sdk:2.30.0'
|
implementation 'com.amplitude:android-sdk:2.30.0'
|
||||||
// Image Management Library
|
// Image Management Library
|
||||||
implementation("io.coil-kt:coil:1.2.2")
|
implementation("io.coil-kt:coil:1.4.0")
|
||||||
implementation("io.coil-kt:coil-gif:1.2.2")
|
implementation("io.coil-kt:coil-gif:1.4.0")
|
||||||
|
|
||||||
//Tests
|
//Tests
|
||||||
testImplementation 'io.kotest:kotest-runner-junit5:4.6.2'
|
testImplementation 'io.kotest:kotest-runner-junit5:4.6.2'
|
||||||
|
|
@ -116,10 +116,12 @@ dependencies {
|
||||||
implementation 'com.nex3z:flow-layout:1.2.2'
|
implementation 'com.nex3z:flow-layout:1.2.2'
|
||||||
|
|
||||||
implementation 'androidx.core:core-ktx:1.6.0'
|
implementation 'androidx.core:core-ktx:1.6.0'
|
||||||
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
|
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
|
||||||
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
|
||||||
implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"
|
implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
|
||||||
|
implementation "androidx.fragment:fragment-ktx:1.3.6"
|
||||||
implementation "androidx.paging:paging-runtime-ktx:3.0.1"
|
implementation "androidx.paging:paging-runtime-ktx:3.0.1"
|
||||||
implementation 'com.plattysoft.leonids:LeonidsLib:1.3.2'
|
implementation 'com.plattysoft.leonids:LeonidsLib:1.3.2'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
|
||||||
|
|
@ -153,8 +155,8 @@ android {
|
||||||
buildConfigField "String", "TESTING_LEVEL", "\"production\""
|
buildConfigField "String", "TESTING_LEVEL", "\"production\""
|
||||||
resConfigs "en", "bg", "de", "en-rGB", "es", "fr", "hr-rHR", "in", "it", "iw", "ja", "ko", "lt", "nl", "pl", "pt-rBR", "pt-rPT", "ru", "tr", "zh", "zh-rTW"
|
resConfigs "en", "bg", "de", "en-rGB", "es", "fr", "hr-rHR", "in", "it", "iw", "ja", "ko", "lt", "nl", "pl", "pt-rBR", "pt-rPT", "ru", "tr", "zh", "zh-rTW"
|
||||||
|
|
||||||
versionCode 3052
|
versionCode 3070
|
||||||
versionName "3.4"
|
versionName "3.4.1"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,8 @@
|
||||||
android:textSize="12sp"/>
|
android:textSize="12sp"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/bottomView"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="30dp"
|
android:layout_height="28dp"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
tools:background="?colorPrimaryDark"
|
tools:background="?colorPrimaryDark"
|
||||||
|
|
@ -73,4 +72,28 @@
|
||||||
tools:text="+1"
|
tools:text="+1"
|
||||||
/>
|
/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/rage_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
tools:background="?colorPrimaryDark"
|
||||||
|
android:paddingStart="@dimen/spacing_medium"
|
||||||
|
android:paddingEnd="@dimen/spacing_large">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/rageIconView"
|
||||||
|
android:layout_width="@dimen/icon_size"
|
||||||
|
android:layout_height="@dimen/icon_size"
|
||||||
|
android:layout_marginEnd="@dimen/spacing_small"/>
|
||||||
|
<com.habitrpg.android.habitica.ui.views.HabiticaProgressBar
|
||||||
|
android:id="@+id/rageBarView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="8dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:paddingTop="@dimen/spacing_medium"
|
||||||
|
android:paddingBottom="@dimen/spacing_small"
|
||||||
|
app:barForegroundColor="@color/yellow_50"
|
||||||
|
app:barBackgroundColor="@color/content_15_alpha"/>
|
||||||
|
</LinearLayout>
|
||||||
</merge>
|
</merge>
|
||||||
|
|
@ -1098,5 +1098,5 @@
|
||||||
<string name="spooky_promo_info_prompt">The Gem Sale is back to haunt the very end of this year’s Fall Gala! This is one last chance to get more Gems than ever, so stock up while it lasts!</string>
|
<string name="spooky_promo_info_prompt">The Gem Sale is back to haunt the very end of this year’s Fall Gala! This is one last chance to get more Gems than ever, so stock up while it lasts!</string>
|
||||||
<string name="spooky_promo_info_instructions">Between October 29th and November 2nd, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!</string>
|
<string name="spooky_promo_info_instructions">Between October 29th and November 2nd, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!</string>
|
||||||
<string name="view_gem_bundles">View Gem Bundles</string>
|
<string name="view_gem_bundles">View Gem Bundles</string>
|
||||||
<string name="fall_promo_info_prompt">The Fall Gala is in full swing so we thought it was the perfect time to introduce our first ever Gem Sale! Now you will get more Gems with each purchase than ever before.</string>
|
<string name="fall_promo_info_prompt">The Fall Gala is in full swing so we thought it was the perfect time for a Gem Sale! Now you will get more Gems with each purchase than ever before.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -1114,7 +1114,7 @@
|
||||||
<string name="how_it_works">How it works</string>
|
<string name="how_it_works">How it works</string>
|
||||||
<string name="limitations">Limitations</string>
|
<string name="limitations">Limitations</string>
|
||||||
<string name="fall_promo_info_instructions">Between %s and %s, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!</string>
|
<string name="fall_promo_info_instructions">Between %s and %s, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!</string>
|
||||||
<string name="fall_promo_info_prompt">The Fall Gala is in full swing so we thought it was the perfect time to introduce our first ever Gem Sale! Now you will get more Gems with each purchase than ever before.</string>
|
<string name="fall_promo_info_prompt">The Fall Gala is in full swing so we thought it was the perfect time for a Gem Sale! Now you will get more Gems with each purchase than ever before.</string>
|
||||||
<string name="view_gem_bundles">View Gem Bundles</string>
|
<string name="view_gem_bundles">View Gem Bundles</string>
|
||||||
<string name="spooky_promo_info_instructions">Between %s and %s, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!</string>
|
<string name="spooky_promo_info_instructions">Between %s and %s, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!</string>
|
||||||
<string name="gems_promo_info_limitations">This promotion only applies during the limited time event. This event starts on %s (12:00 UTC) and will end %s (00:00 UTC). The promo offer is only available when buying Gems for yourself.</string>
|
<string name="gems_promo_info_limitations">This promotion only applies during the limited time event. This event starts on %s (12:00 UTC) and will end %s (00:00 UTC). The promo offer is only available when buying Gems for yourself.</string>
|
||||||
|
|
@ -1183,4 +1183,5 @@
|
||||||
<string name="terms_of_service">Terms of Service</string>
|
<string name="terms_of_service">Terms of Service</string>
|
||||||
<string name="damage_pending">%.01f dmg pending</string>
|
<string name="damage_pending">%.01f dmg pending</string>
|
||||||
<string name="x_remaining">%s remaining</string>
|
<string name="x_remaining">%s remaining</string>
|
||||||
|
<string name="sale_ends_in">Sale ends in %s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,11 @@ import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.database.DatabaseErrorHandler
|
import android.database.DatabaseErrorHandler
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import android.os.Build
|
||||||
import android.os.Build.VERSION.SDK_INT
|
import android.os.Build.VERSION.SDK_INT
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
|
@ -31,6 +33,7 @@ import com.habitrpg.android.habitica.api.HostConfig
|
||||||
import com.habitrpg.android.habitica.components.AppComponent
|
import com.habitrpg.android.habitica.components.AppComponent
|
||||||
import com.habitrpg.android.habitica.components.UserComponent
|
import com.habitrpg.android.habitica.components.UserComponent
|
||||||
import com.habitrpg.android.habitica.data.ApiClient
|
import com.habitrpg.android.habitica.data.ApiClient
|
||||||
|
import com.habitrpg.android.habitica.helpers.LanguageHelper
|
||||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||||
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
|
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
|
||||||
import com.habitrpg.android.habitica.modules.UserModule
|
import com.habitrpg.android.habitica.modules.UserModule
|
||||||
|
|
@ -46,6 +49,7 @@ import org.solovyev.android.checkout.Billing
|
||||||
import org.solovyev.android.checkout.Cache
|
import org.solovyev.android.checkout.Cache
|
||||||
import org.solovyev.android.checkout.Checkout
|
import org.solovyev.android.checkout.Checkout
|
||||||
import org.solovyev.android.checkout.PurchaseVerifier
|
import org.solovyev.android.checkout.PurchaseVerifier
|
||||||
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
// contains all HabiticaApplicationLogic except dagger componentInitialisation
|
// contains all HabiticaApplicationLogic except dagger componentInitialisation
|
||||||
|
|
@ -78,6 +82,7 @@ abstract class HabiticaBaseApplication : Application() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
setupRealm()
|
setupRealm()
|
||||||
setupDagger()
|
setupDagger()
|
||||||
|
setLocale()
|
||||||
setupRemoteConfig()
|
setupRemoteConfig()
|
||||||
setupNotifications()
|
setupNotifications()
|
||||||
createBillingAndCheckout()
|
createBillingAndCheckout()
|
||||||
|
|
@ -117,6 +122,21 @@ abstract class HabiticaBaseApplication : Application() {
|
||||||
checkIfNewVersion()
|
checkIfNewVersion()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setLocale() {
|
||||||
|
val resources = resources
|
||||||
|
val configuration: Configuration = resources.configuration
|
||||||
|
val languageHelper = LanguageHelper(sharedPrefs.getString("language", "en"))
|
||||||
|
if (if (SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
configuration.locales.isEmpty || configuration.locales[0] != languageHelper.locale
|
||||||
|
} else {
|
||||||
|
configuration.locale != languageHelper.locale
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
configuration.setLocale(languageHelper.locale)
|
||||||
|
resources.updateConfiguration(configuration, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected open fun setupRealm() {
|
protected open fun setupRealm() {
|
||||||
Realm.init(this)
|
Realm.init(this)
|
||||||
val builder = RealmConfiguration.Builder()
|
val builder = RealmConfiguration.Builder()
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.habitrpg.android.habitica
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import androidx.core.content.edit
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.firebase.analytics.FirebaseAnalytics
|
import com.google.firebase.analytics.FirebaseAnalytics
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
|
@ -26,32 +27,35 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
|
||||||
private val context: Context
|
private val context: Context
|
||||||
override fun doVerify(purchases: List<Purchase>, requestListener: RequestListener<List<Purchase>>) {
|
override fun doVerify(purchases: List<Purchase>, requestListener: RequestListener<List<Purchase>>) {
|
||||||
val verifiedPurchases: MutableList<Purchase> = ArrayList(purchases.size)
|
val verifiedPurchases: MutableList<Purchase> = ArrayList(purchases.size)
|
||||||
|
val allPurchases = purchases.toMutableList()
|
||||||
for (purchase in purchases) {
|
for (purchase in purchases) {
|
||||||
if (purchasedOrderList.contains(purchase.orderId)) {
|
if (purchasedOrderList.contains(purchase.orderId)) {
|
||||||
verifiedPurchases.add(purchase)
|
verifiedPurchases.add(purchase)
|
||||||
requestListener.onSuccess(verifiedPurchases)
|
processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener)
|
||||||
} else {
|
} else {
|
||||||
when {
|
when {
|
||||||
PurchaseTypes.allGemTypes.contains(purchase.sku) -> {
|
PurchaseTypes.allGemTypes.contains(purchase.sku) -> {
|
||||||
val validationRequest = buildValidationRequest(purchase)
|
val validationRequest = buildValidationRequest(purchase)
|
||||||
apiClient.validatePurchase(validationRequest).subscribe({
|
apiClient.validatePurchase(validationRequest).subscribe({
|
||||||
purchasedOrderList.add(purchase.orderId)
|
purchasedOrderList.add(purchase.orderId)
|
||||||
requestListener.onSuccess(verifiedPurchases)
|
verifiedPurchases.add(purchase)
|
||||||
|
processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener)
|
||||||
val giftedID = removeGift(purchase.sku)
|
val giftedID = removeGift(purchase.sku)
|
||||||
EventBus.getDefault().post(ConsumablePurchasedEvent(purchase, giftedID))
|
EventBus.getDefault().post(ConsumablePurchasedEvent(purchase, giftedID))
|
||||||
}) { throwable: Throwable ->
|
}) { throwable: Throwable ->
|
||||||
handleError(throwable, purchase, requestListener, verifiedPurchases)
|
handleError(throwable, purchase, allPurchases, requestListener, verifiedPurchases)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PurchaseTypes.allSubscriptionNoRenewTypes.contains(purchase.sku) -> {
|
PurchaseTypes.allSubscriptionNoRenewTypes.contains(purchase.sku) -> {
|
||||||
val validationRequest = buildValidationRequest(purchase)
|
val validationRequest = buildValidationRequest(purchase)
|
||||||
apiClient.validateNoRenewSubscription(validationRequest).subscribe({
|
apiClient.validateNoRenewSubscription(validationRequest).subscribe({
|
||||||
purchasedOrderList.add(purchase.orderId)
|
purchasedOrderList.add(purchase.orderId)
|
||||||
requestListener.onSuccess(verifiedPurchases)
|
verifiedPurchases.add(purchase)
|
||||||
|
processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener)
|
||||||
val giftedID = removeGift(purchase.sku)
|
val giftedID = removeGift(purchase.sku)
|
||||||
EventBus.getDefault().post(ConsumablePurchasedEvent(purchase, giftedID))
|
EventBus.getDefault().post(ConsumablePurchasedEvent(purchase, giftedID))
|
||||||
}) { throwable: Throwable ->
|
}) { throwable: Throwable ->
|
||||||
handleError(throwable, purchase, requestListener, verifiedPurchases)
|
handleError(throwable, purchase, allPurchases, requestListener, verifiedPurchases)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PurchaseTypes.allSubscriptionTypes.contains(purchase.sku) -> {
|
PurchaseTypes.allSubscriptionTypes.contains(purchase.sku) -> {
|
||||||
|
|
@ -63,20 +67,35 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
|
||||||
apiClient.validateSubscription(validationRequest).subscribe({
|
apiClient.validateSubscription(validationRequest).subscribe({
|
||||||
purchasedOrderList.add(purchase.orderId)
|
purchasedOrderList.add(purchase.orderId)
|
||||||
verifiedPurchases.add(purchase)
|
verifiedPurchases.add(purchase)
|
||||||
requestListener.onSuccess(verifiedPurchases)
|
processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener)
|
||||||
FirebaseAnalytics.getInstance(context).logEvent("user_subscribed", null)
|
FirebaseAnalytics.getInstance(context).logEvent("user_subscribed", null)
|
||||||
EventBus.getDefault().post(UserSubscribedEvent())
|
EventBus.getDefault().post(UserSubscribedEvent())
|
||||||
}) { throwable: Throwable ->
|
}) { throwable: Throwable ->
|
||||||
handleError(throwable, purchase, requestListener, verifiedPurchases)
|
handleError(throwable, purchase, allPurchases, requestListener, verifiedPurchases)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val edit = preferences?.edit()
|
preferences?.edit {
|
||||||
edit?.putStringSet(PURCHASED_PRODUCTS_KEY, purchasedOrderList)
|
putStringSet(PURCHASED_PRODUCTS_KEY, purchasedOrderList)
|
||||||
edit?.apply()
|
}
|
||||||
savePendingGifts()
|
}
|
||||||
|
|
||||||
|
private fun processedPurchase(
|
||||||
|
purchase: Purchase,
|
||||||
|
allPurchases: MutableList<Purchase>,
|
||||||
|
verifiedPurchases: MutableList<Purchase>,
|
||||||
|
requestListener: RequestListener<List<Purchase>>
|
||||||
|
) {
|
||||||
|
allPurchases.remove(purchase)
|
||||||
|
if (allPurchases.isEmpty()) {
|
||||||
|
if (verifiedPurchases.isEmpty()) {
|
||||||
|
requestListener.onError(ResponseCodes.ERROR, Exception())
|
||||||
|
} else {
|
||||||
|
requestListener.onSuccess(verifiedPurchases)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildValidationRequest(purchase: Purchase): PurchaseValidationRequest {
|
private fun buildValidationRequest(purchase: Purchase): PurchaseValidationRequest {
|
||||||
|
|
@ -92,13 +111,17 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
|
||||||
return validationRequest
|
return validationRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleError(throwable: Throwable, purchase: Purchase, requestListener: RequestListener<List<Purchase>>, verifiedPurchases: MutableList<Purchase>) {
|
private fun handleError(throwable: Throwable, purchase: Purchase,
|
||||||
|
allPurchases: MutableList<Purchase>,
|
||||||
|
requestListener: RequestListener<List<Purchase>>,
|
||||||
|
verifiedPurchases: MutableList<Purchase>) {
|
||||||
(throwable as? HttpException)?.let { error ->
|
(throwable as? HttpException)?.let { error ->
|
||||||
if (error.code() == 401) {
|
if (error.code() == 401) {
|
||||||
val res = apiClient.getErrorResponse(throwable)
|
val res = apiClient.getErrorResponse(throwable)
|
||||||
if (res.message != null && res.message == "RECEIPT_ALREADY_USED") {
|
if (res.message != null && res.message == "RECEIPT_ALREADY_USED") {
|
||||||
purchasedOrderList.add(purchase.orderId)
|
purchasedOrderList.add(purchase.orderId)
|
||||||
requestListener.onSuccess(verifiedPurchases)
|
verifiedPurchases.add(purchase)
|
||||||
|
processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener)
|
||||||
EventBus.getDefault().post(ConsumablePurchasedEvent(purchase))
|
EventBus.getDefault().post(ConsumablePurchasedEvent(purchase))
|
||||||
removeGift(purchase.sku)
|
removeGift(purchase.sku)
|
||||||
return
|
return
|
||||||
|
|
@ -106,7 +129,7 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FirebaseCrashlytics.getInstance().recordException(throwable)
|
FirebaseCrashlytics.getInstance().recordException(throwable)
|
||||||
requestListener.onError(ResponseCodes.ERROR, Exception())
|
processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadPendingGifts(): MutableMap<String?, String?> {
|
private fun loadPendingGifts(): MutableMap<String?, String?> {
|
||||||
|
|
@ -131,6 +154,7 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
|
||||||
private const val PENDING_GIFTS_KEY = "PENDING_GIFTS"
|
private const val PENDING_GIFTS_KEY = "PENDING_GIFTS"
|
||||||
private var pendingGifts: MutableMap<String?, String?> = HashMap()
|
private var pendingGifts: MutableMap<String?, String?> = HashMap()
|
||||||
private var preferences: SharedPreferences? = null
|
private var preferences: SharedPreferences? = null
|
||||||
|
|
||||||
fun addGift(sku: String?, userID: String?) {
|
fun addGift(sku: String?, userID: String?) {
|
||||||
pendingGifts[sku] = userID
|
pendingGifts[sku] = userID
|
||||||
savePendingGifts()
|
savePendingGifts()
|
||||||
|
|
@ -145,10 +169,9 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
|
||||||
private fun savePendingGifts() {
|
private fun savePendingGifts() {
|
||||||
val jsonObject = JSONObject(pendingGifts as Map<*, *>)
|
val jsonObject = JSONObject(pendingGifts as Map<*, *>)
|
||||||
val jsonString = jsonObject.toString()
|
val jsonString = jsonObject.toString()
|
||||||
val editor = preferences?.edit()
|
preferences?.edit {
|
||||||
editor?.remove(PENDING_GIFTS_KEY)
|
putString(PENDING_GIFTS_KEY, jsonString)
|
||||||
editor?.putString(PENDING_GIFTS_KEY, jsonString)
|
}
|
||||||
editor?.apply()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,7 @@ interface ApiService {
|
||||||
fun seenMessages(@Path("gid") groupId: String): Flowable<HabitResponse<Void>>
|
fun seenMessages(@Path("gid") groupId: String): Flowable<HabitResponse<Void>>
|
||||||
|
|
||||||
@POST("groups/{gid}/invite")
|
@POST("groups/{gid}/invite")
|
||||||
fun inviteToGroup(@Path("gid") groupId: String, @Body inviteData: Map<String, Any>): Flowable<HabitResponse<Void>>
|
fun inviteToGroup(@Path("gid") groupId: String, @Body inviteData: Map<String, Any>): Flowable<HabitResponse<List<Void>>>
|
||||||
|
|
||||||
@POST("groups/{gid}/reject-invite")
|
@POST("groups/{gid}/reject-invite")
|
||||||
fun rejectGroupInvite(@Path("gid") groupId: String): Flowable<HabitResponse<Void>>
|
fun rejectGroupInvite(@Path("gid") groupId: String): Flowable<HabitResponse<Void>>
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,7 @@ import com.habitrpg.android.habitica.ui.fragments.tasks.TeamBoardFragment;
|
||||||
import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel;
|
import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel;
|
||||||
import com.habitrpg.android.habitica.ui.viewmodels.InboxViewModel;
|
import com.habitrpg.android.habitica.ui.viewmodels.InboxViewModel;
|
||||||
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel;
|
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel;
|
||||||
|
import com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment.EquipmentOverviewViewModel;
|
||||||
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog;
|
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog;
|
||||||
import com.habitrpg.android.habitica.ui.views.shops.PurchaseDialog;
|
import com.habitrpg.android.habitica.ui.views.shops.PurchaseDialog;
|
||||||
import com.habitrpg.android.habitica.ui.views.social.ChatBarView;
|
import com.habitrpg.android.habitica.ui.views.social.ChatBarView;
|
||||||
|
|
@ -346,4 +347,6 @@ public interface UserComponent {
|
||||||
void inject(@NotNull PromoWebFragment promoWebFragment);
|
void inject(@NotNull PromoWebFragment promoWebFragment);
|
||||||
|
|
||||||
void inject(@NotNull ItemDialogFragment itemDialogFragment);
|
void inject(@NotNull ItemDialogFragment itemDialogFragment);
|
||||||
|
|
||||||
|
void inject(@NotNull EquipmentOverviewViewModel equipmentOverviewViewModel);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ interface ApiClient {
|
||||||
|
|
||||||
fun seenMessages(groupId: String): Flowable<Void>
|
fun seenMessages(groupId: String): Flowable<Void>
|
||||||
|
|
||||||
fun inviteToGroup(groupId: String, inviteData: Map<String, Any>): Flowable<Void>
|
fun inviteToGroup(groupId: String, inviteData: Map<String, Any>): Flowable<List<Void>>
|
||||||
|
|
||||||
fun rejectGroupInvite(groupId: String): Flowable<Void>
|
fun rejectGroupInvite(groupId: String): Flowable<Void>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ interface SocialRepository : BaseRepository {
|
||||||
fun getGroupMembers(id: String): Flowable<out List<Member>>
|
fun getGroupMembers(id: String): Flowable<out List<Member>>
|
||||||
fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): Flowable<List<Member>>
|
fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): Flowable<List<Member>>
|
||||||
|
|
||||||
fun inviteToGroup(id: String, inviteData: Map<String, Any>): Flowable<Void>
|
fun inviteToGroup(id: String, inviteData: Map<String, Any>): Flowable<List<Void>>
|
||||||
|
|
||||||
fun getMember(userId: String?): Flowable<Member>
|
fun getMember(userId: String?): Flowable<Member>
|
||||||
fun getMemberWithUsername(username: String?): Flowable<Member>
|
fun getMemberWithUsername(username: String?): Flowable<Member>
|
||||||
|
|
@ -60,6 +60,8 @@ interface SocialRepository : BaseRepository {
|
||||||
|
|
||||||
fun markPrivateMessagesRead(user: User?): Flowable<Void>
|
fun markPrivateMessagesRead(user: User?): Flowable<Void>
|
||||||
|
|
||||||
|
fun markSomePrivateMessagesAsRead(user: User?, messages: List<ChatMessage>)
|
||||||
|
|
||||||
fun transferGroupOwnership(groupID: String, userID: String): Flowable<Group>
|
fun transferGroupOwnership(groupID: String, userID: String): Flowable<Group>
|
||||||
fun removeMemberFromGroup(groupID: String, userID: String): Flowable<List<Member>>
|
fun removeMemberFromGroup(groupID: String, userID: String): Flowable<List<Member>>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import com.habitrpg.android.habitica.api.GSonFactoryCreator
|
||||||
import com.habitrpg.android.habitica.api.HostConfig
|
import com.habitrpg.android.habitica.api.HostConfig
|
||||||
import com.habitrpg.android.habitica.api.Server
|
import com.habitrpg.android.habitica.api.Server
|
||||||
import com.habitrpg.android.habitica.data.ApiClient
|
import com.habitrpg.android.habitica.data.ApiClient
|
||||||
import com.habitrpg.android.habitica.events.ConsumablePurchasedEvent
|
|
||||||
import com.habitrpg.android.habitica.events.ShowConnectionProblemEvent
|
import com.habitrpg.android.habitica.events.ShowConnectionProblemEvent
|
||||||
import com.habitrpg.android.habitica.helpers.NotificationsManager
|
import com.habitrpg.android.habitica.helpers.NotificationsManager
|
||||||
import com.habitrpg.android.habitica.models.*
|
import com.habitrpg.android.habitica.models.*
|
||||||
|
|
@ -554,7 +553,7 @@ class ApiClientImpl // private OnHabitsAPIResult mResultListener;
|
||||||
return apiService.seenMessages(groupId).compose(configureApiCallObserver())
|
return apiService.seenMessages(groupId).compose(configureApiCallObserver())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inviteToGroup(groupId: String, inviteData: Map<String, Any>): Flowable<Void> {
|
override fun inviteToGroup(groupId: String, inviteData: Map<String, Any>): Flowable<List<Void>> {
|
||||||
return apiService.inviteToGroup(groupId, inviteData).compose(configureApiCallObserver())
|
return apiService.inviteToGroup(groupId, inviteData).compose(configureApiCallObserver())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -241,7 +241,7 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
|
||||||
.doOnNext { members -> localRepository.saveGroupMembers(id, members) }
|
.doOnNext { members -> localRepository.saveGroupMembers(id, members) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inviteToGroup(id: String, inviteData: Map<String, Any>): Flowable<Void> = apiClient.inviteToGroup(id, inviteData)
|
override fun inviteToGroup(id: String, inviteData: Map<String, Any>): Flowable<List<Void>> = apiClient.inviteToGroup(id, inviteData)
|
||||||
|
|
||||||
override fun getMember(userId: String?): Flowable<Member> {
|
override fun getMember(userId: String?): Flowable<Member> {
|
||||||
return if (userId == null) {
|
return if (userId == null) {
|
||||||
|
|
@ -264,12 +264,31 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markPrivateMessagesRead(user: User?): Flowable<Void> {
|
override fun markPrivateMessagesRead(user: User?): Flowable<Void> {
|
||||||
|
if (user?.isManaged == true) {
|
||||||
|
localRepository.modify(user) {
|
||||||
|
it.inbox?.hasUserSeenInbox = true
|
||||||
|
}
|
||||||
|
}
|
||||||
return apiClient.markPrivateMessagesRead()
|
return apiClient.markPrivateMessagesRead()
|
||||||
.doOnNext {
|
}
|
||||||
if (user?.isManaged == true) {
|
|
||||||
localRepository.modify(user) { it.inbox?.newMessages = 0 }
|
override fun markSomePrivateMessagesAsRead(user: User?, messages: List<ChatMessage>) {
|
||||||
|
if (user?.isManaged == true) {
|
||||||
|
val numOfUnseenMessages = messages.count { !it.isSeen }
|
||||||
|
localRepository.modify(user) {
|
||||||
|
val numOfNewMessagesFromInbox = it.inbox?.newMessages ?: 0
|
||||||
|
if (numOfNewMessagesFromInbox > numOfUnseenMessages) {
|
||||||
|
it.inbox?.newMessages = numOfNewMessagesFromInbox - numOfUnseenMessages
|
||||||
|
} else {
|
||||||
|
it.inbox?.newMessages = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
for (message in messages.filter { it.isManaged && !it.isSeen }) {
|
||||||
|
localRepository.modify(message) {
|
||||||
|
it.isSeen = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getUserGroups(type: String?): Flowable<out List<Group>> = localRepository.getUserGroups(userID, type)
|
override fun getUserGroups(type: String?): Flowable<out List<Group>> = localRepository.getUserGroups(userID, type)
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,13 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
|
||||||
|
|
||||||
override fun saveInboxMessages(userID: String, recipientID: String, messages: List<ChatMessage>, page: Int) {
|
override fun saveInboxMessages(userID: String, recipientID: String, messages: List<ChatMessage>, page: Int) {
|
||||||
messages.forEach { it.userID = userID }
|
messages.forEach { it.userID = userID }
|
||||||
|
for (message in messages) {
|
||||||
|
val existingMessage = realm.where(ChatMessage::class.java)
|
||||||
|
.equalTo("id", message.id)
|
||||||
|
.findAll()
|
||||||
|
.firstOrNull()
|
||||||
|
message.isSeen = existingMessage != null
|
||||||
|
}
|
||||||
save(messages)
|
save(messages)
|
||||||
if (page != 0) return
|
if (page != 0) return
|
||||||
val existingMessages = realm.where(ChatMessage::class.java).equalTo("isInboxMessage", true).equalTo("uuid", recipientID).findAll()
|
val existingMessages = realm.where(ChatMessage::class.java).equalTo("isInboxMessage", true).equalTo("uuid", recipientID).findAll()
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,13 @@ import io.realm.Realm
|
||||||
|
|
||||||
class RealmTagLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), TagLocalRepository {
|
class RealmTagLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), TagLocalRepository {
|
||||||
override fun deleteTag(tagID: String) {
|
override fun deleteTag(tagID: String) {
|
||||||
val tag = realm.where(Tag::class.java).equalTo("id", tagID).findFirst()
|
val tags = realm.where(Tag::class.java).equalTo("id", tagID).findAll()
|
||||||
executeTransaction { tag?.deleteFromRealm() }
|
executeTransaction { tags.deleteAllFromRealm() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTags(userId: String): Flowable<out List<Tag>> {
|
override fun getTags(userId: String): Flowable<out List<Tag>> {
|
||||||
return RxJavaBridge.toV3Flowable(realm.where(Tag::class.java).equalTo("userId", userId).findAll().asFlowable())
|
return RxJavaBridge.toV3Flowable(
|
||||||
|
realm.where(Tag::class.java).equalTo("userId", userId).findAll().asFlowable()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,17 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
executeTransaction { realm1 -> realm1.insertOrUpdate(user) }
|
executeTransaction { realm1 -> realm1.insertOrUpdate(user) }
|
||||||
|
removeOldTags(user.id ?: "", user.tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeOldTags(userId: String, onlineTags: List<Tag>) {
|
||||||
|
val tags = realm.where(Tag::class.java).equalTo("userId", userId).findAll().createSnapshot()
|
||||||
|
val tagsToDelete = tags.filterNot { onlineTags.contains(it) }
|
||||||
|
executeTransaction {
|
||||||
|
for (tag in tagsToDelete) {
|
||||||
|
tag.deleteFromRealm()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun saveMessages(messages: List<ChatMessage>) {
|
override fun saveMessages(messages: List<ChatMessage>) {
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,9 @@ class AppConfigManager(contentRepository: ContentRepository?) {
|
||||||
if (promo == null && remoteConfig.getString("activePromo").isNotBlank()) {
|
if (promo == null && remoteConfig.getString("activePromo").isNotBlank()) {
|
||||||
promo = getHabiticaPromotionFromKey(remoteConfig.getString("activePromo"), null, null)
|
promo = getHabiticaPromotionFromKey(remoteConfig.getString("activePromo"), null, null)
|
||||||
}
|
}
|
||||||
|
if (promo?.isActive != true) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
if (promo is HabiticaWebPromotion) {
|
if (promo is HabiticaWebPromotion) {
|
||||||
promo.url = surveyURL()
|
promo.url = surveyURL()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
||||||
import com.habitrpg.android.habitica.proxy.AnalyticsManager
|
import com.habitrpg.android.habitica.proxy.AnalyticsManager
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.solovyev.android.checkout.*
|
import org.solovyev.android.checkout.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
@ -74,16 +76,18 @@ open class PurchaseHandler(activity: Activity, val analyticsManager: AnalyticsMa
|
||||||
return purchases.skus.firstOrNull()
|
return purchases.skus.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loadInventory(type: String, skus: List<String>): Inventory.Products? = suspendCoroutine { cont ->
|
private suspend fun loadInventory(type: String, skus: List<String>): Inventory.Products? = withContext(Dispatchers.Main) {
|
||||||
val request = Inventory.Request.create().loadAllPurchases().loadSkus(type, skus)
|
suspendCoroutine { cont ->
|
||||||
try {
|
val request = Inventory.Request.create().loadAllPurchases().loadSkus(type, skus)
|
||||||
inventory?.load(request) {
|
try {
|
||||||
cont.resume(it)
|
inventory?.load(request) {
|
||||||
|
cont.resume(it)
|
||||||
|
}
|
||||||
|
} catch (e: NullPointerException) {
|
||||||
|
cont.resumeWithException(e)
|
||||||
}
|
}
|
||||||
} catch (e: NullPointerException) {
|
if (inventory == null) cont.resume(null)
|
||||||
cont.resumeWithException(e)
|
|
||||||
}
|
}
|
||||||
if (inventory == null) cont.resume(null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun purchaseSubscription(sku: Sku, onSuccess: (() -> Unit)) {
|
fun purchaseSubscription(sku: Sku, onSuccess: (() -> Unit)) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.habitrpg.android.habitica.helpers
|
package com.habitrpg.android.habitica.helpers
|
||||||
|
|
||||||
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
||||||
import io.reactivex.rxjava3.core.Maybe
|
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -17,9 +16,10 @@ class SoundManager {
|
||||||
HabiticaBaseApplication.userComponent?.inject(this)
|
HabiticaBaseApplication.userComponent?.inject(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun preloadAllFiles(): Maybe<List<SoundFile>> {
|
fun preloadAllFiles() {
|
||||||
|
loadedSoundFiles.clear()
|
||||||
if (soundTheme == SoundThemeOff) {
|
if (soundTheme == SoundThemeOff) {
|
||||||
return Maybe.empty()
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val soundFiles = ArrayList<SoundFile>()
|
val soundFiles = ArrayList<SoundFile>()
|
||||||
|
|
@ -33,7 +33,8 @@ class SoundManager {
|
||||||
soundFiles.add(SoundFile(soundTheme, SoundPlusHabit))
|
soundFiles.add(SoundFile(soundTheme, SoundPlusHabit))
|
||||||
soundFiles.add(SoundFile(soundTheme, SoundReward))
|
soundFiles.add(SoundFile(soundTheme, SoundReward))
|
||||||
soundFiles.add(SoundFile(soundTheme, SoundTodo))
|
soundFiles.add(SoundFile(soundTheme, SoundTodo))
|
||||||
return soundFileLoader.download(soundFiles).toMaybe()
|
soundFileLoader.download(soundFiles)
|
||||||
|
.subscribe({}, RxErrorHandler.handleEmptyError())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadAndPlayAudio(type: String) {
|
fun loadAndPlayAudio(type: String) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.habitrpg.android.habitica.helpers.notifications
|
package com.habitrpg.android.habitica.helpers.notifications
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
@ -40,6 +41,7 @@ class GroupActivityNotification(context: Context, identifier: String?) : Habitic
|
||||||
oldMessages.add(data)
|
oldMessages.add(data)
|
||||||
return super.configureNotificationBuilder(data)
|
return super.configureNotificationBuilder(data)
|
||||||
.setStyle(style)
|
.setStyle(style)
|
||||||
|
.setCategory(Notification.CATEGORY_MESSAGE)
|
||||||
.setExtras(bundleOf(Pair("messages", bundleOf(Pair("messages", oldMessages)))))
|
.setExtras(bundleOf(Pair("messages", bundleOf(Pair("messages", oldMessages)))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,8 +57,8 @@ class GroupActivityNotification(context: Context, identifier: String?) : Habitic
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setNotificationActions(data: Map<String, String>) {
|
override fun setNotificationActions(notificationId: Int, data: Map<String, String>) {
|
||||||
super.setNotificationActions(data)
|
super.setNotificationActions(notificationId, data)
|
||||||
val groupID = data["groupID"] ?: return
|
val groupID = data["groupID"] ?: return
|
||||||
|
|
||||||
val actionName = context.getString(R.string.group_message_reply)
|
val actionName = context.getString(R.string.group_message_reply)
|
||||||
|
|
@ -68,6 +70,7 @@ class GroupActivityNotification(context: Context, identifier: String?) : Habitic
|
||||||
val intent = Intent(context, LocalNotificationActionReceiver::class.java)
|
val intent = Intent(context, LocalNotificationActionReceiver::class.java)
|
||||||
intent.action = actionName
|
intent.action = actionName
|
||||||
intent.putExtra("groupID", groupID)
|
intent.putExtra("groupID", groupID)
|
||||||
|
intent.putExtra("NOTIFICATION_ID", notificationId)
|
||||||
val replyPendingIntent: PendingIntent =
|
val replyPendingIntent: PendingIntent =
|
||||||
PendingIntent.getBroadcast(
|
PendingIntent.getBroadcast(
|
||||||
context, groupID.hashCode(),
|
context, groupID.hashCode(),
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,18 @@ class GuildInviteLocalNotification(context: Context, identifier: String?) : Habi
|
||||||
intent.putExtra("groupID", data?.get("groupID"))
|
intent.putExtra("groupID", data?.get("groupID"))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setNotificationActions(data: Map<String, String>) {
|
override fun setNotificationActions(notificationId: Int, data: Map<String, String>) {
|
||||||
super.setNotificationActions(data)
|
super.setNotificationActions(notificationId, data)
|
||||||
val res = context.resources
|
val res = context.resources
|
||||||
|
|
||||||
val acceptInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
|
val acceptInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
|
||||||
acceptInviteIntent.action = res.getString(R.string.accept_guild_invite)
|
acceptInviteIntent.action = res.getString(R.string.accept_guild_invite)
|
||||||
acceptInviteIntent.putExtra("groupID", this.data?.get("groupID"))
|
val groupID = data.get("groupID")
|
||||||
|
acceptInviteIntent.putExtra("groupID", groupID)
|
||||||
|
acceptInviteIntent.putExtra("NOTIFICATION_ID", notificationId)
|
||||||
val pendingIntentAccept = PendingIntent.getBroadcast(
|
val pendingIntentAccept = PendingIntent.getBroadcast(
|
||||||
context,
|
context,
|
||||||
3000,
|
groupID.hashCode(),
|
||||||
acceptInviteIntent,
|
acceptInviteIntent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
|
|
@ -33,10 +35,11 @@ class GuildInviteLocalNotification(context: Context, identifier: String?) : Habi
|
||||||
|
|
||||||
val rejectInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
|
val rejectInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
|
||||||
rejectInviteIntent.action = res.getString(R.string.reject_guild_invite)
|
rejectInviteIntent.action = res.getString(R.string.reject_guild_invite)
|
||||||
rejectInviteIntent.putExtra("groupID", this.data?.get("groupID"))
|
rejectInviteIntent.putExtra("groupID", groupID)
|
||||||
|
acceptInviteIntent.putExtra("NOTIFICATION_ID", notificationId)
|
||||||
val pendingIntentReject = PendingIntent.getBroadcast(
|
val pendingIntentReject = PendingIntent.getBroadcast(
|
||||||
context,
|
context,
|
||||||
2000,
|
groupID.hashCode() + 1,
|
||||||
rejectInviteIntent,
|
rejectInviteIntent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -44,19 +44,21 @@ abstract class HabiticaLocalNotification(protected var context: Context, protect
|
||||||
notificationBuilder = notificationBuilder.setContentText(message)
|
notificationBuilder = notificationBuilder.setContentText(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setNotificationActions(data)
|
val notificationId = getNotificationID(data)
|
||||||
|
this.setNotificationActions(notificationId, data)
|
||||||
|
|
||||||
val notificationManager = NotificationManagerCompat.from(context)
|
val notificationManager = NotificationManagerCompat.from(context)
|
||||||
notificationManager.notify(getNotificationID(data), notificationBuilder.build())
|
notificationManager.notify(notificationId, notificationBuilder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setExtras(data: Map<String, String>) {
|
fun setExtras(data: Map<String, String>) {
|
||||||
this.data = data
|
this.data = data
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun setNotificationActions(data: Map<String, String>) {
|
protected open fun setNotificationActions(notificationId: Int, data: Map<String, String>) {
|
||||||
val intent = Intent(context, MainActivity::class.java)
|
val intent = Intent(context, MainActivity::class.java)
|
||||||
configureMainIntent(intent)
|
configureMainIntent(intent)
|
||||||
|
intent.putExtra("NOTIFICATION_ID", notificationId)
|
||||||
val pendingIntent = PendingIntent.getActivity(
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
context,
|
context,
|
||||||
3000,
|
3000,
|
||||||
|
|
|
||||||
|
|
@ -11,16 +11,18 @@ import com.habitrpg.android.habitica.receivers.LocalNotificationActionReceiver
|
||||||
*/
|
*/
|
||||||
class PartyInviteLocalNotification(context: Context, identifier: String?) : HabiticaLocalNotification(context, identifier) {
|
class PartyInviteLocalNotification(context: Context, identifier: String?) : HabiticaLocalNotification(context, identifier) {
|
||||||
|
|
||||||
override fun setNotificationActions(data: Map<String, String>) {
|
override fun setNotificationActions(notificationId: Int, data: Map<String, String>) {
|
||||||
super.setNotificationActions(data)
|
super.setNotificationActions(notificationId, data)
|
||||||
val res = context.resources
|
val res = context.resources
|
||||||
|
|
||||||
val acceptInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
|
val acceptInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
|
||||||
acceptInviteIntent.action = res.getString(R.string.accept_party_invite)
|
acceptInviteIntent.action = res.getString(R.string.accept_party_invite)
|
||||||
acceptInviteIntent.putExtra("groupID", this.data?.get("groupID"))
|
val groupID = data.get("groupID")
|
||||||
|
acceptInviteIntent.putExtra("groupID", groupID)
|
||||||
|
acceptInviteIntent.putExtra("NOTIFICATION_ID", notificationId)
|
||||||
val pendingIntentAccept = PendingIntent.getBroadcast(
|
val pendingIntentAccept = PendingIntent.getBroadcast(
|
||||||
context,
|
context,
|
||||||
3000,
|
groupID.hashCode(),
|
||||||
acceptInviteIntent,
|
acceptInviteIntent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
|
|
@ -28,10 +30,11 @@ class PartyInviteLocalNotification(context: Context, identifier: String?) : Habi
|
||||||
|
|
||||||
val rejectInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
|
val rejectInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
|
||||||
rejectInviteIntent.action = res.getString(R.string.reject_party_invite)
|
rejectInviteIntent.action = res.getString(R.string.reject_party_invite)
|
||||||
rejectInviteIntent.putExtra("groupID", this.data?.get("groupID"))
|
rejectInviteIntent.putExtra("groupID", groupID)
|
||||||
|
rejectInviteIntent.putExtra("NOTIFICATION_ID", notificationId)
|
||||||
val pendingIntentReject = PendingIntent.getBroadcast(
|
val pendingIntentReject = PendingIntent.getBroadcast(
|
||||||
context,
|
context,
|
||||||
2000,
|
groupID.hashCode() + 1,
|
||||||
rejectInviteIntent,
|
rejectInviteIntent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,16 @@ class QuestInviteLocalNotification(context: Context, identifier: String?) : Habi
|
||||||
return 1000
|
return 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setNotificationActions(data: Map<String, String>) {
|
override fun setNotificationActions(notificationId: Int, data: Map<String, String>) {
|
||||||
super.setNotificationActions(data)
|
super.setNotificationActions(notificationId, data)
|
||||||
val res = context.resources
|
val res = context.resources
|
||||||
|
|
||||||
val acceptInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
|
val acceptInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
|
||||||
acceptInviteIntent.action = res.getString(R.string.accept_quest_invite)
|
acceptInviteIntent.action = res.getString(R.string.accept_quest_invite)
|
||||||
|
acceptInviteIntent.putExtra("NOTIFICATION_ID", notificationId)
|
||||||
val pendingIntentAccept = PendingIntent.getBroadcast(
|
val pendingIntentAccept = PendingIntent.getBroadcast(
|
||||||
context,
|
context,
|
||||||
3000,
|
3001,
|
||||||
acceptInviteIntent,
|
acceptInviteIntent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
|
|
@ -31,9 +32,10 @@ class QuestInviteLocalNotification(context: Context, identifier: String?) : Habi
|
||||||
|
|
||||||
val rejectInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
|
val rejectInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
|
||||||
rejectInviteIntent.action = res.getString(R.string.reject_quest_invite)
|
rejectInviteIntent.action = res.getString(R.string.reject_quest_invite)
|
||||||
|
rejectInviteIntent.putExtra("NOTIFICATION_ID", notificationId)
|
||||||
val pendingIntentReject = PendingIntent.getBroadcast(
|
val pendingIntentReject = PendingIntent.getBroadcast(
|
||||||
context,
|
context,
|
||||||
2000,
|
2001,
|
||||||
rejectInviteIntent,
|
rejectInviteIntent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.habitrpg.android.habitica.helpers.notifications
|
package com.habitrpg.android.habitica.helpers.notifications
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
@ -36,6 +37,7 @@ class ReceivedPrivateMessageLocalNotification(context: Context, identifier: Stri
|
||||||
}
|
}
|
||||||
notification = notification
|
notification = notification
|
||||||
.setContentTitle(notificationTitle)
|
.setContentTitle(notificationTitle)
|
||||||
|
.setCategory(Notification.CATEGORY_MESSAGE)
|
||||||
.setStyle(style)
|
.setStyle(style)
|
||||||
title = null
|
title = null
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -48,8 +50,8 @@ class ReceivedPrivateMessageLocalNotification(context: Context, identifier: Stri
|
||||||
return data["senderName"].hashCode()
|
return data["senderName"].hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setNotificationActions(data: Map<String, String>) {
|
override fun setNotificationActions(notificationId: Int, data: Map<String, String>) {
|
||||||
super.setNotificationActions(data)
|
super.setNotificationActions(notificationId, data)
|
||||||
val senderID = data["replyTo"] ?: return
|
val senderID = data["replyTo"] ?: return
|
||||||
|
|
||||||
val actionName = context.getString(R.string.inbox_message_reply)
|
val actionName = context.getString(R.string.inbox_message_reply)
|
||||||
|
|
@ -61,6 +63,7 @@ class ReceivedPrivateMessageLocalNotification(context: Context, identifier: Stri
|
||||||
val intent = Intent(context, LocalNotificationActionReceiver::class.java)
|
val intent = Intent(context, LocalNotificationActionReceiver::class.java)
|
||||||
intent.action = actionName
|
intent.action = actionName
|
||||||
intent.putExtra("senderID", senderID)
|
intent.putExtra("senderID", senderID)
|
||||||
|
intent.putExtra("NOTIFICATION_ID", notificationId)
|
||||||
val replyPendingIntent: PendingIntent =
|
val replyPendingIntent: PendingIntent =
|
||||||
PendingIntent.getBroadcast(
|
PendingIntent.getBroadcast(
|
||||||
context, senderID.hashCode(),
|
context, senderID.hashCode(),
|
||||||
|
|
|
||||||
|
|
@ -109,28 +109,28 @@ constructor(
|
||||||
return textView
|
return textView
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNotificationAndAddStatsToUserAsText(xp: Double, hp: Double, gold: Double, mp: Double): Pair<SpannableStringBuilder, SnackbarDisplayType> {
|
fun getNotificationAndAddStatsToUserAsText(xp: Double?, hp: Double?, gold: Double?, mp: Double?): Pair<SpannableStringBuilder, SnackbarDisplayType> {
|
||||||
val builder = SpannableStringBuilder()
|
val builder = SpannableStringBuilder()
|
||||||
var displayType = SnackbarDisplayType.NORMAL
|
var displayType = SnackbarDisplayType.NORMAL
|
||||||
|
|
||||||
if (xp > 0) {
|
if ((xp ?: 0.0) > 0) {
|
||||||
builder.append(" + ").append(xp.round(2).toString()).append(" Exp")
|
builder.append(" + ").append(xp?.round(2).toString()).append(" Exp")
|
||||||
}
|
}
|
||||||
if (hp != 0.0) {
|
if (hp != 0.0) {
|
||||||
displayType = SnackbarDisplayType.FAILURE
|
displayType = SnackbarDisplayType.FAILURE
|
||||||
builder.append(" - ").append(abs(hp.round(2)).toString()).append(" Health")
|
builder.append(" - ").append(abs(hp?.round(2) ?: 0.0).toString()).append(" Health")
|
||||||
}
|
}
|
||||||
if (gold != 0.0) {
|
if (gold != 0.0) {
|
||||||
if (gold > 0) {
|
if ((gold ?: 0.0) > 0) {
|
||||||
builder.append(" + ").append(gold.round(2).toString())
|
builder.append(" + ").append(gold?.round(2).toString())
|
||||||
} else if (gold < 0) {
|
} else if ((gold ?: 0.0) < 0) {
|
||||||
displayType = SnackbarDisplayType.FAILURE
|
displayType = SnackbarDisplayType.FAILURE
|
||||||
builder.append(" - ").append(abs(gold.round(2)).toString())
|
builder.append(" - ").append(abs(gold?.round(2) ?: 0.0).toString())
|
||||||
}
|
}
|
||||||
builder.append(" Gold")
|
builder.append(" Gold")
|
||||||
}
|
}
|
||||||
if (mp > 0) {
|
if ((mp ?: 0.0) > 0) {
|
||||||
builder.append(" + ").append(mp.round(2).toString()).append(" Mana")
|
builder.append(" + ").append(mp?.round(2).toString()).append(" Mana")
|
||||||
}
|
}
|
||||||
|
|
||||||
return Pair(builder, displayType)
|
return Pair(builder, displayType)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,11 @@ enum class PromoType {
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class HabiticaPromotion {
|
abstract class HabiticaPromotion {
|
||||||
|
val isActive: Boolean
|
||||||
|
get() {
|
||||||
|
val now = Date()
|
||||||
|
return startDate.before(now) && endDate.after(now)
|
||||||
|
}
|
||||||
abstract val identifier: String
|
abstract val identifier: String
|
||||||
abstract val promoType: PromoType
|
abstract val promoType: PromoType
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,8 @@ open class ChatMessage : RealmObject(), BaseMainObject {
|
||||||
val formattedUsername: String?
|
val formattedUsername: String?
|
||||||
get() = if (username != null) "@$username" else null
|
get() = if (username != null) "@$username" else null
|
||||||
|
|
||||||
|
var isSeen: Boolean = true
|
||||||
|
|
||||||
fun userLikesMessage(userId: String?): Boolean {
|
fun userLikesMessage(userId: String?): Boolean {
|
||||||
return likes?.any { userId == it.id } ?: false
|
return likes?.any { userId == it.id } ?: false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,5 @@ open class Inbox : RealmObject(), BaseObject {
|
||||||
var optOut: Boolean = false
|
var optOut: Boolean = false
|
||||||
var blocks: RealmList<String> = RealmList()
|
var blocks: RealmList<String> = RealmList()
|
||||||
var newMessages: Int = 0
|
var newMessages: Int = 0
|
||||||
|
var hasUserSeenInbox: Boolean = false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import android.app.NotificationManager
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.text.Spannable
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.app.RemoteInput
|
import androidx.core.app.RemoteInput
|
||||||
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
||||||
|
|
@ -13,37 +16,44 @@ import com.habitrpg.android.habitica.data.SocialRepository
|
||||||
import com.habitrpg.android.habitica.data.TaskRepository
|
import com.habitrpg.android.habitica.data.TaskRepository
|
||||||
import com.habitrpg.android.habitica.data.UserRepository
|
import com.habitrpg.android.habitica.data.UserRepository
|
||||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||||
|
import com.habitrpg.android.habitica.interactors.NotifyUserUseCase
|
||||||
import com.habitrpg.android.habitica.models.user.User
|
import com.habitrpg.android.habitica.models.user.User
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class LocalNotificationActionReceiver : BroadcastReceiver() {
|
class LocalNotificationActionReceiver : BroadcastReceiver() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var userRepository: UserRepository
|
lateinit var userRepository: UserRepository
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var socialRepository: SocialRepository
|
lateinit var socialRepository: SocialRepository
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var taskRepository: TaskRepository
|
lateinit var taskRepository: TaskRepository
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var apiClient: ApiClient
|
lateinit var apiClient: ApiClient
|
||||||
|
|
||||||
private var user: User? = null
|
private var user: User? = null
|
||||||
private var groupID: String? = null
|
private val groupID: String?
|
||||||
private var senderID: String? = null
|
get() = intent?.extras?.getString("groupID")
|
||||||
|
private val senderID: String?
|
||||||
|
get() = intent?.extras?.getString("senderID")
|
||||||
|
private val taskID: String?
|
||||||
|
get() = intent?.extras?.getString("taskID")
|
||||||
private var context: Context? = null
|
private var context: Context? = null
|
||||||
private var intent: Intent? = null
|
private var intent: Intent? = null
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
HabiticaBaseApplication.userComponent?.inject(this)
|
HabiticaBaseApplication.userComponent?.inject(this)
|
||||||
this.intent = intent
|
this.intent = intent
|
||||||
groupID = intent.extras?.getString("groupID")
|
|
||||||
senderID = intent.extras?.getString("senderID")
|
|
||||||
this.context = context
|
this.context = context
|
||||||
handleLocalNotificationAction(intent.action)
|
handleLocalNotificationAction(intent.action)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLocalNotificationAction(action: String?) {
|
private fun handleLocalNotificationAction(action: String?) {
|
||||||
val notificationManager = this.context?.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
|
val notificationManager =
|
||||||
notificationManager?.cancelAll()
|
this.context?.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
|
||||||
|
notificationManager?.cancel(intent?.extras?.getInt("NOTIFICATION_ID") ?: -1)
|
||||||
when (action) {
|
when (action) {
|
||||||
context?.getString(R.string.accept_party_invite) -> {
|
context?.getString(R.string.accept_party_invite) -> {
|
||||||
groupID?.let {
|
groupID?.let {
|
||||||
|
|
@ -52,7 +62,8 @@ class LocalNotificationActionReceiver : BroadcastReceiver() {
|
||||||
}
|
}
|
||||||
context?.getString(R.string.reject_party_invite) -> {
|
context?.getString(R.string.reject_party_invite) -> {
|
||||||
groupID?.let {
|
groupID?.let {
|
||||||
socialRepository.rejectGroupInvite(it).subscribe({ }, RxErrorHandler.handleEmptyError())
|
socialRepository.rejectGroupInvite(it)
|
||||||
|
.subscribe({ }, RxErrorHandler.handleEmptyError())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context?.getString(R.string.accept_quest_invite) -> {
|
context?.getString(R.string.accept_quest_invite) -> {
|
||||||
|
|
@ -68,7 +79,8 @@ class LocalNotificationActionReceiver : BroadcastReceiver() {
|
||||||
}
|
}
|
||||||
context?.getString(R.string.reject_guild_invite) -> {
|
context?.getString(R.string.reject_guild_invite) -> {
|
||||||
groupID?.let {
|
groupID?.let {
|
||||||
socialRepository.rejectGroupInvite(it).subscribe({ }, RxErrorHandler.handleEmptyError())
|
socialRepository.rejectGroupInvite(it)
|
||||||
|
.subscribe({ }, RxErrorHandler.handleEmptyError())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context?.getString(R.string.group_message_reply) -> {
|
context?.getString(R.string.group_message_reply) -> {
|
||||||
|
|
@ -76,7 +88,9 @@ class LocalNotificationActionReceiver : BroadcastReceiver() {
|
||||||
getMessageText(context?.getString(R.string.group_message_reply))?.let { message ->
|
getMessageText(context?.getString(R.string.group_message_reply))?.let { message ->
|
||||||
socialRepository.postGroupChat(it, message).subscribe(
|
socialRepository.postGroupChat(it, message).subscribe(
|
||||||
{
|
{
|
||||||
context?.let { c -> NotificationManagerCompat.from(c).cancel(it.hashCode()) }
|
context?.let { c ->
|
||||||
|
NotificationManagerCompat.from(c).cancel(it.hashCode())
|
||||||
|
}
|
||||||
},
|
},
|
||||||
RxErrorHandler.handleEmptyError()
|
RxErrorHandler.handleEmptyError()
|
||||||
)
|
)
|
||||||
|
|
@ -86,19 +100,33 @@ class LocalNotificationActionReceiver : BroadcastReceiver() {
|
||||||
context?.getString(R.string.inbox_message_reply) -> {
|
context?.getString(R.string.inbox_message_reply) -> {
|
||||||
senderID?.let {
|
senderID?.let {
|
||||||
getMessageText(context?.getString(R.string.inbox_message_reply))?.let { message ->
|
getMessageText(context?.getString(R.string.inbox_message_reply))?.let { message ->
|
||||||
socialRepository.postPrivateMessage(it, message).subscribe({ }, RxErrorHandler.handleEmptyError())
|
socialRepository.postPrivateMessage(it, message)
|
||||||
|
.subscribe({ }, RxErrorHandler.handleEmptyError())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context?.getString(R.string.complete_task_action) -> {
|
context?.getString(R.string.complete_task_action) -> {
|
||||||
intent?.extras?.getString("taskID")?.let {
|
taskID?.let {
|
||||||
taskRepository.taskChecked(null, it, up = true, force = false) {
|
taskRepository.taskChecked(null, it, up = true, force = false) {
|
||||||
}.subscribe({}, RxErrorHandler.handleEmptyError())
|
}.subscribe({
|
||||||
|
val pair = NotifyUserUseCase.getNotificationAndAddStatsToUserAsText(
|
||||||
|
it?.experienceDelta,
|
||||||
|
it?.healthDelta,
|
||||||
|
it?.goldDelta,
|
||||||
|
it?.manaDelta
|
||||||
|
)
|
||||||
|
showToast(pair.first)
|
||||||
|
}, RxErrorHandler.handleEmptyError())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showToast(text: Spannable) {
|
||||||
|
val toast = Toast.makeText(context, text, Toast.LENGTH_LONG)
|
||||||
|
toast.show()
|
||||||
|
}
|
||||||
|
|
||||||
private fun getMessageText(key: String?): String? {
|
private fun getMessageText(key: String?): String? {
|
||||||
return RemoteInput.getResultsFromIntent(intent)?.getCharSequence(key)?.toString()
|
return RemoteInput.getResultsFromIntent(intent)?.getCharSequence(key)?.toString()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package com.habitrpg.android.habitica.receivers
|
package com.habitrpg.android.habitica.receivers
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.media.RingtoneManager
|
import android.media.RingtoneManager
|
||||||
|
import android.os.Build
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
||||||
|
|
@ -62,21 +64,29 @@ class TaskReceiver : BroadcastReceiver() {
|
||||||
val pendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), intent, 0)
|
val pendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), intent, 0)
|
||||||
val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
||||||
|
|
||||||
val notificationBuilder = NotificationCompat.Builder(context, "default")
|
var notificationBuilder = NotificationCompat.Builder(context, "default")
|
||||||
.setSmallIcon(R.drawable.ic_gryphon_white)
|
.setSmallIcon(R.drawable.ic_gryphon_white)
|
||||||
.setContentTitle(task.text)
|
.setContentTitle(task.text)
|
||||||
|
.setStyle(NotificationCompat.BigTextStyle()
|
||||||
|
.bigText(task.notes))
|
||||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||||
.setSound(soundUri)
|
.setSound(soundUri)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
notificationBuilder = notificationBuilder.setCategory(Notification.CATEGORY_REMINDER)
|
||||||
|
}
|
||||||
|
|
||||||
if (task.type == Task.TYPE_DAILY || task.type == Task.TYPE_TODO) {
|
if (task.type == Task.TYPE_DAILY || task.type == Task.TYPE_TODO) {
|
||||||
val completeIntent = Intent(context, LocalNotificationActionReceiver::class.java)
|
val completeIntent = Intent(context, LocalNotificationActionReceiver::class.java).apply {
|
||||||
completeIntent.action = context.getString(R.string.complete_task_action)
|
action = context.getString(R.string.complete_task_action)
|
||||||
completeIntent.putExtra("taskID", task.id)
|
putExtra("taskID", task.id)
|
||||||
|
putExtra("NOTIFICATION_ID", task.id.hashCode())
|
||||||
|
}
|
||||||
val pendingIntentComplete = PendingIntent.getBroadcast(
|
val pendingIntentComplete = PendingIntent.getBroadcast(
|
||||||
context,
|
context,
|
||||||
3000,
|
task.id.hashCode(),
|
||||||
completeIntent,
|
completeIntent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import androidx.core.view.marginStart
|
||||||
import androidx.core.view.marginTop
|
import androidx.core.view.marginTop
|
||||||
import coil.clear
|
import coil.clear
|
||||||
import coil.load
|
import coil.load
|
||||||
|
import coil.target.ImageViewTarget
|
||||||
import com.habitrpg.android.habitica.BuildConfig
|
import com.habitrpg.android.habitica.BuildConfig
|
||||||
import com.habitrpg.android.habitica.R
|
import com.habitrpg.android.habitica.R
|
||||||
import com.habitrpg.android.habitica.extensions.dpToPx
|
import com.habitrpg.android.habitica.extensions.dpToPx
|
||||||
|
|
@ -143,17 +144,15 @@ class AvatarView : FrameLayout {
|
||||||
|
|
||||||
imageView.load(DataBindingUtils.BASE_IMAGE_URL + DataBindingUtils.getFullFilename(layerName)) {
|
imageView.load(DataBindingUtils.BASE_IMAGE_URL + DataBindingUtils.getFullFilename(layerName)) {
|
||||||
allowHardware(false)
|
allowHardware(false)
|
||||||
target(
|
target(object : ImageViewTarget(imageView) {
|
||||||
{},
|
override fun onError(error: Drawable?) {
|
||||||
{
|
super.onError(error)
|
||||||
onLayerComplete()
|
onLayerComplete()
|
||||||
},
|
}
|
||||||
{
|
|
||||||
if (imageView.tag != layerName) {
|
override fun onSuccess(result: Drawable) {
|
||||||
return@target
|
super.onSuccess(result)
|
||||||
}
|
val bounds = getLayerBounds(layerKey, layerName, result)
|
||||||
val bounds = getLayerBounds(layerKey, layerName, it)
|
|
||||||
imageView.setImageDrawable(it)
|
|
||||||
imageView.imageMatrix = avatarMatrix
|
imageView.imageMatrix = avatarMatrix
|
||||||
val layoutParams = imageView.layoutParams as? LayoutParams
|
val layoutParams = imageView.layoutParams as? LayoutParams
|
||||||
layoutParams?.topMargin = bounds.top
|
layoutParams?.topMargin = bounds.top
|
||||||
|
|
@ -163,7 +162,7 @@ class AvatarView : FrameLayout {
|
||||||
imageView.layoutParams = layoutParams
|
imageView.layoutParams = layoutParams
|
||||||
onLayerComplete()
|
onLayerComplete()
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (i < (imageViewHolder.size)) {
|
while (i < (imageViewHolder.size)) {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
|
|
@ -61,7 +62,7 @@ abstract class BaseActivity : AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val languageHelper = LanguageHelper(sharedPreferences.getString("language", "en"))
|
val languageHelper = LanguageHelper(sharedPreferences.getString("language", "en"))
|
||||||
resources.forceLocale(languageHelper.locale)
|
resources.forceLocale(this, languageHelper.locale)
|
||||||
delegate.localNightMode = when (sharedPreferences.getString("theme_mode", "system")) {
|
delegate.localNightMode = when (sharedPreferences.getString("theme_mode", "system")) {
|
||||||
"light" -> AppCompatDelegate.MODE_NIGHT_NO
|
"light" -> AppCompatDelegate.MODE_NIGHT_NO
|
||||||
"dark" -> AppCompatDelegate.MODE_NIGHT_YES
|
"dark" -> AppCompatDelegate.MODE_NIGHT_YES
|
||||||
|
|
@ -77,6 +78,13 @@ abstract class BaseActivity : AppCompatActivity() {
|
||||||
compositeSubscription = CompositeDisposable()
|
compositeSubscription = CompositeDisposable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onRestart() {
|
||||||
|
super.onRestart()
|
||||||
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
val languageHelper = LanguageHelper(sharedPreferences.getString("language", "en"))
|
||||||
|
resources.forceLocale(this, languageHelper.locale)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
EventBus.getDefault().register(this)
|
EventBus.getDefault().register(this)
|
||||||
|
|
@ -222,9 +230,12 @@ abstract class BaseActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Resources.forceLocale(locale: Locale) {
|
private fun Resources.forceLocale(activity: BaseActivity, locale: Locale) {
|
||||||
Locale.setDefault(locale)
|
Locale.setDefault(locale)
|
||||||
val configuration = Configuration()
|
val configuration = Configuration()
|
||||||
configuration.setLocale(locale)
|
configuration.setLocale(locale)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
activity.createConfigurationContext(configuration)
|
||||||
|
}
|
||||||
updateConfiguration(configuration, displayMetrics)
|
updateConfiguration(configuration, displayMetrics)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -847,7 +847,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
fun showWonAchievementDialog(event: ShowWonChallengeDialog) {
|
fun showWonChallengeDialog(event: ShowWonChallengeDialog) {
|
||||||
retrieveUser(true)
|
retrieveUser(true)
|
||||||
lifecycleScope.launch(context = Dispatchers.Main) {
|
lifecycleScope.launch(context = Dispatchers.Main) {
|
||||||
val dialog = WonChallengeDialog(this@MainActivity)
|
val dialog = WonChallengeDialog(this@MainActivity)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ class PrefsActivity : BaseActivity(), PreferenceFragmentCompat.OnPreferenceStart
|
||||||
setupToolbar(findViewById(R.id.toolbar))
|
setupToolbar(findViewById(R.id.toolbar))
|
||||||
|
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.add(R.id.fragment_container, PreferencesFragment())
|
.replace(R.id.fragment_container, PreferencesFragment())
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||||
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
||||||
import com.habitrpg.android.habitica.R
|
import com.habitrpg.android.habitica.R
|
||||||
import com.habitrpg.android.habitica.data.ContentRepository
|
import com.habitrpg.android.habitica.data.ContentRepository
|
||||||
|
|
@ -28,12 +29,14 @@ import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||||
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
||||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||||
import com.habitrpg.android.habitica.models.WorldState
|
import com.habitrpg.android.habitica.models.WorldState
|
||||||
|
import com.habitrpg.android.habitica.models.WorldStateEvent
|
||||||
import com.habitrpg.android.habitica.models.inventory.Item
|
import com.habitrpg.android.habitica.models.inventory.Item
|
||||||
import com.habitrpg.android.habitica.models.inventory.Quest
|
import com.habitrpg.android.habitica.models.inventory.Quest
|
||||||
import com.habitrpg.android.habitica.models.inventory.QuestContent
|
import com.habitrpg.android.habitica.models.inventory.QuestContent
|
||||||
import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion
|
import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion
|
||||||
import com.habitrpg.android.habitica.models.promotions.PromoType
|
import com.habitrpg.android.habitica.models.promotions.PromoType
|
||||||
import com.habitrpg.android.habitica.models.social.Group
|
import com.habitrpg.android.habitica.models.social.Group
|
||||||
|
import com.habitrpg.android.habitica.models.user.Inbox
|
||||||
import com.habitrpg.android.habitica.models.user.User
|
import com.habitrpg.android.habitica.models.user.User
|
||||||
import com.habitrpg.android.habitica.ui.activities.MainActivity
|
import com.habitrpg.android.habitica.ui.activities.MainActivity
|
||||||
import com.habitrpg.android.habitica.ui.activities.NotificationsActivity
|
import com.habitrpg.android.habitica.ui.activities.NotificationsActivity
|
||||||
|
|
@ -98,6 +101,7 @@ class NavigationDrawerFragment : DialogFragment() {
|
||||||
private fun updateQuestDisplay() {
|
private fun updateQuestDisplay() {
|
||||||
val quest = this.quest
|
val quest = this.quest
|
||||||
val questContent = this.questContent
|
val questContent = this.questContent
|
||||||
|
return
|
||||||
if (quest == null || questContent == null || !quest.active) {
|
if (quest == null || questContent == null || !quest.active) {
|
||||||
binding?.questMenuView?.visibility = View.GONE
|
binding?.questMenuView?.visibility = View.GONE
|
||||||
context?.let {
|
context?.let {
|
||||||
|
|
@ -125,20 +129,6 @@ class NavigationDrawerFragment : DialogFragment() {
|
||||||
}
|
}
|
||||||
binding?.questMenuView?.setBackgroundColor(context?.getThemeColor(R.attr.colorPrimaryDark) ?: 0)
|
binding?.questMenuView?.setBackgroundColor(context?.getThemeColor(R.attr.colorPrimaryDark) ?: 0)
|
||||||
|
|
||||||
/* Reenable this once the boss art can be displayed correctly.
|
|
||||||
|
|
||||||
val preferences = context?.getSharedPreferences("collapsible_sections", 0)
|
|
||||||
if (preferences?.getBoolean("boss_art_collapsed", false) == true) {
|
|
||||||
questMenuView.hideBossArt()
|
|
||||||
} else {
|
|
||||||
questMenuView.showBossArt()
|
|
||||||
}*/
|
|
||||||
//binding?.questMenuView?.hideBossArt()
|
|
||||||
|
|
||||||
/*getItemWithIdentifier(SIDEBAR_TAVERN)?.let { tavern ->
|
|
||||||
tavern.subtitle = context?.getString(R.string.active_world_boss)
|
|
||||||
adapter.updateItem(tavern)
|
|
||||||
}*/
|
|
||||||
binding?.questMenuView?.setOnClickListener {
|
binding?.questMenuView?.setOnClickListener {
|
||||||
setSelection(R.id.partyFragment)
|
setSelection(R.id.partyFragment)
|
||||||
/*val context = this.context
|
/*val context = this.context
|
||||||
|
|
@ -181,6 +171,7 @@ class NavigationDrawerFragment : DialogFragment() {
|
||||||
|
|
||||||
binding?.recyclerView?.adapter = adapter
|
binding?.recyclerView?.adapter = adapter
|
||||||
binding?.recyclerView?.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
|
binding?.recyclerView?.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
|
||||||
|
(binding?.recyclerView?.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false
|
||||||
initializeMenuItems()
|
initializeMenuItems()
|
||||||
|
|
||||||
subscriptions?.add(
|
subscriptions?.add(
|
||||||
|
|
@ -237,12 +228,12 @@ class NavigationDrawerFragment : DialogFragment() {
|
||||||
{ pair ->
|
{ pair ->
|
||||||
val gearEvent = pair.first.events.firstOrNull { it.gear }
|
val gearEvent = pair.first.events.firstOrNull { it.gear }
|
||||||
createUpdatingJob("seasonal", {
|
createUpdatingJob("seasonal", {
|
||||||
gearEvent?.end?.after(Date()) == true || pair.second.isNotEmpty()
|
gearEvent?.isCurrentlyActive == true || pair.second.isNotEmpty()
|
||||||
}, {
|
}, {
|
||||||
val diff = (gearEvent?.end?.time ?: 0) - Date().time
|
val diff = (gearEvent?.end?.time ?: 0) - Date().time
|
||||||
if (diff < (Duration.hours(1).inWholeMilliseconds)) Duration.seconds(1) else Duration.minutes(1)
|
if (diff < (Duration.hours(1).inWholeMilliseconds)) Duration.seconds(1) else Duration.minutes(1)
|
||||||
}) {
|
}) {
|
||||||
updateSeasonalMenuEntries(pair.first, pair.second)
|
updateSeasonalMenuEntries(gearEvent, pair.second)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RxErrorHandler.handleEmptyError()
|
RxErrorHandler.handleEmptyError()
|
||||||
|
|
@ -278,6 +269,7 @@ class NavigationDrawerFragment : DialogFragment() {
|
||||||
it.quest?.key ?: ""
|
it.quest?.key ?: ""
|
||||||
}
|
}
|
||||||
.flatMapMaybe { inventoryRepository.getQuestContent(it).firstElement() }
|
.flatMapMaybe { inventoryRepository.getQuestContent(it).firstElement() }
|
||||||
|
.filter { (it.boss?.hp ?: 0) > 0 }
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{
|
{
|
||||||
questContent = it
|
questContent = it
|
||||||
|
|
@ -310,7 +302,7 @@ class NavigationDrawerFragment : DialogFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSeasonalMenuEntries(worldState: WorldState, items: List<Item>) {
|
private fun updateSeasonalMenuEntries(gearEvent: WorldStateEvent?, items: List<Item>) {
|
||||||
val market = getItemWithIdentifier(SIDEBAR_SHOPS_MARKET) ?: return
|
val market = getItemWithIdentifier(SIDEBAR_SHOPS_MARKET) ?: return
|
||||||
if (items.isNotEmpty() && items.firstOrNull()?.event?.end?.after(Date()) == true) {
|
if (items.isNotEmpty() && items.firstOrNull()?.event?.end?.after(Date()) == true) {
|
||||||
market.pillText = context?.getString(R.string.something_new)
|
market.pillText = context?.getString(R.string.something_new)
|
||||||
|
|
@ -323,8 +315,7 @@ class NavigationDrawerFragment : DialogFragment() {
|
||||||
|
|
||||||
val shop = getItemWithIdentifier(SIDEBAR_SHOPS_SEASONAL) ?: return
|
val shop = getItemWithIdentifier(SIDEBAR_SHOPS_SEASONAL) ?: return
|
||||||
shop.pillText = context?.getString(R.string.open)
|
shop.pillText = context?.getString(R.string.open)
|
||||||
val gearEvent = worldState.events.firstOrNull { it.gear }
|
if (gearEvent?.isCurrentlyActive == true) {
|
||||||
if (gearEvent?.end?.after(Date()) == true) {
|
|
||||||
shop.isVisible = true
|
shop.isVisible = true
|
||||||
shop.subtitle = context?.getString(R.string.open_for, gearEvent.end?.getShortRemainingString())
|
shop.subtitle = context?.getString(R.string.open_for, gearEvent.end?.getShortRemainingString())
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -334,7 +325,7 @@ class NavigationDrawerFragment : DialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateUser(user: User) {
|
private fun updateUser(user: User) {
|
||||||
setMessagesCount(user.inbox?.newMessages ?: 0)
|
setMessagesCount(user.inbox)
|
||||||
setSettingsCount(if (user.flags?.verifiedUsername != true) 1 else 0)
|
setSettingsCount(if (user.flags?.verifiedUsername != true) 1 else 0)
|
||||||
setDisplayName(user.profile?.name)
|
setDisplayName(user.profile?.name)
|
||||||
setUsername(user.formattedUsername)
|
setUsername(user.formattedUsername)
|
||||||
|
|
@ -629,12 +620,23 @@ class NavigationDrawerFragment : DialogFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setMessagesCount(unreadMessages: Int) {
|
private fun setMessagesCount(inbox: Inbox?) {
|
||||||
if (unreadMessages == 0) {
|
val numOfUnreadMessages = inbox?.newMessages ?: 0
|
||||||
binding?.messagesBadge?.visibility = View.GONE
|
if (numOfUnreadMessages != 0) {
|
||||||
} else {
|
|
||||||
binding?.messagesBadge?.visibility = View.VISIBLE
|
binding?.messagesBadge?.visibility = View.VISIBLE
|
||||||
binding?.messagesBadge?.text = unreadMessages.toString()
|
binding?.messagesBadge?.text = numOfUnreadMessages.toString()
|
||||||
|
context?.let {
|
||||||
|
val color = if (inbox?.hasUserSeenInbox != true) {
|
||||||
|
it.getThemeColor(R.attr.colorAccent)
|
||||||
|
} else {
|
||||||
|
ContextCompat.getColor(it, R.color.gray_200)
|
||||||
|
}
|
||||||
|
val background = binding?.messagesBadge?.background as? GradientDrawable
|
||||||
|
background?.color = ColorStateList.valueOf(color)
|
||||||
|
binding?.messagesBadge?.setTextColor(ContextCompat.getColor(it, R.color.white))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding?.messagesBadge?.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -652,13 +654,9 @@ class NavigationDrawerFragment : DialogFragment() {
|
||||||
activePromo = configManager.activePromo()
|
activePromo = configManager.activePromo()
|
||||||
val promoItem = getItemWithIdentifier(SIDEBAR_PROMO) ?: return
|
val promoItem = getItemWithIdentifier(SIDEBAR_PROMO) ?: return
|
||||||
activePromo?.let { activePromo ->
|
activePromo?.let { activePromo ->
|
||||||
if (sharedPreferences.getBoolean("hide${activePromo.identifier}", false)) {
|
promoItem.isVisible =
|
||||||
promoItem.isVisible = true
|
!sharedPreferences.getBoolean("hide${activePromo.identifier}", false)
|
||||||
adapter.activePromo = activePromo
|
adapter.activePromo = activePromo
|
||||||
} else {
|
|
||||||
promoItem.isVisible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
var promotedItem: HabiticaDrawerItem? = null
|
var promotedItem: HabiticaDrawerItem? = null
|
||||||
if (activePromo.promoType == PromoType.GEMS_AMOUNT || activePromo.promoType == PromoType.GEMS_PRICE) {
|
if (activePromo.promoType == PromoType.GEMS_AMOUNT || activePromo.promoType == PromoType.GEMS_PRICE) {
|
||||||
promotedItem = getItemWithIdentifier(SIDEBAR_GEMS)
|
promotedItem = getItemWithIdentifier(SIDEBAR_GEMS)
|
||||||
|
|
@ -670,13 +668,18 @@ class NavigationDrawerFragment : DialogFragment() {
|
||||||
promotedItem.pillText = context?.getString(R.string.sale)
|
promotedItem.pillText = context?.getString(R.string.sale)
|
||||||
promotedItem.pillBackground = context?.let { activePromo.pillBackgroundDrawable(it) }
|
promotedItem.pillBackground = context?.let { activePromo.pillBackgroundDrawable(it) }
|
||||||
createUpdatingJob(activePromo.promoType.name, {
|
createUpdatingJob(activePromo.promoType.name, {
|
||||||
activePromo.endDate.after(Date())
|
activePromo.isActive
|
||||||
}, {
|
}, {
|
||||||
val diff = activePromo.endDate.time - Date().time
|
val diff = activePromo.endDate.time - Date().time
|
||||||
if (diff < (Duration.hours(1).inWholeMilliseconds)) Duration.seconds(1) else Duration.minutes(1)
|
if (diff < (Duration.hours(1).inWholeMilliseconds)) Duration.seconds(1) else Duration.minutes(1)
|
||||||
}) {
|
}) {
|
||||||
promotedItem.subtitle = context?.getString(R.string.x_remaining, activePromo.endDate.getShortRemainingString())
|
if (activePromo.isActive) {
|
||||||
updateItem(promotedItem)
|
promotedItem.subtitle = context?.getString(R.string.sale_ends_in, activePromo.endDate.getShortRemainingString())
|
||||||
|
updateItem(promotedItem)
|
||||||
|
} else {
|
||||||
|
promotedItem.subtitle = null
|
||||||
|
updateItem(promotedItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} ?: run {
|
} ?: run {
|
||||||
promoItem.isVisible = false
|
promoItem.isVisible = false
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ class EquipmentDetailFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
var type: String? = null
|
var type: String? = null
|
||||||
var typeText: String? = null
|
|
||||||
var equippedGear: String? = null
|
var equippedGear: String? = null
|
||||||
var isCostume: Boolean? = null
|
var isCostume: Boolean? = null
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,38 +4,26 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import com.habitrpg.android.habitica.components.UserComponent
|
import com.habitrpg.android.habitica.components.UserComponent
|
||||||
import com.habitrpg.android.habitica.data.InventoryRepository
|
|
||||||
import com.habitrpg.android.habitica.databinding.FragmentEquipmentOverviewBinding
|
import com.habitrpg.android.habitica.databinding.FragmentEquipmentOverviewBinding
|
||||||
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
||||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
|
||||||
import com.habitrpg.android.habitica.models.user.Gear
|
import com.habitrpg.android.habitica.models.user.Gear
|
||||||
import com.habitrpg.android.habitica.models.user.User
|
import com.habitrpg.android.habitica.models.user.Outfit
|
||||||
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
|
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
|
||||||
import javax.inject.Inject
|
import com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment.EquipmentOverviewViewModel
|
||||||
|
import com.habitrpg.android.habitica.ui.views.equipment.EquipmentOverviewView
|
||||||
|
|
||||||
class EquipmentOverviewFragment : BaseMainFragment<FragmentEquipmentOverviewBinding>() {
|
class EquipmentOverviewFragment : BaseMainFragment<FragmentEquipmentOverviewBinding>() {
|
||||||
|
|
||||||
|
private val viewModel: EquipmentOverviewViewModel by viewModels()
|
||||||
|
|
||||||
override var binding: FragmentEquipmentOverviewBinding? = null
|
override var binding: FragmentEquipmentOverviewBinding? = null
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentEquipmentOverviewBinding {
|
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentEquipmentOverviewBinding {
|
||||||
return FragmentEquipmentOverviewBinding.inflate(inflater, container, false)
|
return FragmentEquipmentOverviewBinding.inflate(inflater, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var inventoryRepository: InventoryRepository
|
|
||||||
|
|
||||||
override var user: User?
|
|
||||||
get() = super.user
|
|
||||||
set(value) {
|
|
||||||
super.user = value
|
|
||||||
if (this::inventoryRepository.isInitialized) {
|
|
||||||
value?.items?.gear?.let {
|
|
||||||
updateGearData(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
|
@ -46,22 +34,24 @@ class EquipmentOverviewFragment : BaseMainFragment<FragmentEquipmentOverviewBind
|
||||||
displayEquipmentDetailList(type, equipped, true)
|
displayEquipmentDetailList(type, equipped, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding?.autoEquipSwitch?.isChecked = user?.preferences?.autoEquip ?: false
|
binding?.autoEquipSwitch?.setOnCheckedChangeListener { _, isChecked ->
|
||||||
binding?.costumeSwitch?.isChecked = user?.preferences?.costume ?: false
|
if (isChecked == viewModel.user.value?.preferences?.autoEquip) return@setOnCheckedChangeListener
|
||||||
|
viewModel.updateUser("preferences.autoEquip", isChecked) }
|
||||||
|
binding?.costumeSwitch?.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
if (isChecked == viewModel.user.value?.preferences?.costume) return@setOnCheckedChangeListener
|
||||||
|
viewModel.updateUser("preferences.costume", isChecked) }
|
||||||
|
|
||||||
binding?.autoEquipSwitch?.setOnCheckedChangeListener { _, isChecked -> userRepository.updateUser("preferences.autoEquip", isChecked).subscribe({ }, RxErrorHandler.handleEmptyError()) }
|
viewModel.user.observe(viewLifecycleOwner) {
|
||||||
binding?.costumeSwitch?.setOnCheckedChangeListener { _, isChecked -> userRepository.updateUser("preferences.costume", isChecked).subscribe({ }, RxErrorHandler.handleEmptyError()) }
|
it?.items?.gear?.let {
|
||||||
|
updateGearData(it)
|
||||||
|
}
|
||||||
|
binding?.autoEquipSwitch?.isChecked = user?.preferences?.autoEquip ?: false
|
||||||
|
binding?.costumeSwitch?.isChecked = user?.preferences?.costume ?: false
|
||||||
|
|
||||||
user?.items?.gear?.let {
|
binding?.costumeView?.isEnabled = user?.preferences?.costume == true
|
||||||
updateGearData(it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
inventoryRepository.close()
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun injectFragment(component: UserComponent) {
|
override fun injectFragment(component: UserComponent) {
|
||||||
component.inject(this)
|
component.inject(this)
|
||||||
}
|
}
|
||||||
|
|
@ -71,31 +61,17 @@ class EquipmentOverviewFragment : BaseMainFragment<FragmentEquipmentOverviewBind
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateGearData(gear: Gear) {
|
private fun updateGearData(gear: Gear) {
|
||||||
if (gear.equipped?.weapon?.isNotEmpty() == true) {
|
updateOutfit(binding?.battlegearView, gear.equipped)
|
||||||
compositeSubscription.add(
|
updateOutfit(binding?.costumeView, gear.costume)
|
||||||
inventoryRepository.getEquipment(gear.equipped?.weapon ?: "").firstElement()
|
}
|
||||||
.subscribe(
|
|
||||||
{
|
private fun updateOutfit(view: EquipmentOverviewView?, outfit: Outfit?) {
|
||||||
binding?.battlegearView?.updateData(gear.equipped, it.twoHanded)
|
if (outfit?.weapon?.isNotEmpty() == true) {
|
||||||
},
|
viewModel.getGear(outfit.weapon) {
|
||||||
RxErrorHandler.handleEmptyError()
|
view?.updateData(outfit, it.twoHanded)
|
||||||
)
|
}
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
binding?.battlegearView?.updateData(gear.equipped)
|
view?.updateData(outfit)
|
||||||
}
|
|
||||||
if (gear.costume?.weapon?.isNotEmpty() == true) {
|
|
||||||
compositeSubscription.add(
|
|
||||||
inventoryRepository.getEquipment(gear.costume?.weapon ?: "").firstElement()
|
|
||||||
.subscribe(
|
|
||||||
{
|
|
||||||
binding?.costumeView?.updateData(gear.costume, it.twoHanded)
|
|
||||||
},
|
|
||||||
RxErrorHandler.handleEmptyError()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
binding?.costumeView?.updateData(gear.costume)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GemsPurchaseFragment : BaseFragment<FragmentGemPurchaseBinding>(), GemPurchaseActivity.CheckoutFragment {
|
class GemsPurchaseFragment : BaseFragment<FragmentGemPurchaseBinding>(), GemPurchaseActivity.CheckoutFragment {
|
||||||
|
|
@ -98,8 +99,10 @@ class GemsPurchaseFragment : BaseFragment<FragmentGemPurchaseBinding>(), GemPurc
|
||||||
override fun setupCheckout() {
|
override fun setupCheckout() {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val skus = purchaseHandler?.getAllGemSKUs()
|
val skus = purchaseHandler?.getAllGemSKUs()
|
||||||
for (sku in skus ?: emptyList()) {
|
withContext(Dispatchers.Main) {
|
||||||
updateButtonLabel(sku.id.code, sku.price)
|
for (sku in skus ?: emptyList()) {
|
||||||
|
updateButtonLabel(sku.id.code, sku.price)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import com.habitrpg.android.habitica.ui.fragments.BaseFragment
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GiftPurchaseGemsFragment : BaseFragment<FragmentGiftGemPurchaseBinding>() {
|
class GiftPurchaseGemsFragment : BaseFragment<FragmentGiftGemPurchaseBinding>() {
|
||||||
|
|
@ -63,8 +64,10 @@ class GiftPurchaseGemsFragment : BaseFragment<FragmentGiftGemPurchaseBinding>()
|
||||||
fun setupCheckout() {
|
fun setupCheckout() {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val skus = purchaseHandler?.getAllGemSKUs()
|
val skus = purchaseHandler?.getAllGemSKUs()
|
||||||
for (sku in skus ?: emptyList()) {
|
withContext(Dispatchers.Main) {
|
||||||
updateButtonLabel(sku.id.code, sku.price)
|
for (sku in skus ?: emptyList()) {
|
||||||
|
updateButtonLabel(sku.id.code, sku.price)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import com.habitrpg.android.habitica.ui.views.subscriptions.SubscriptionOptionVi
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.solovyev.android.checkout.Inventory
|
import org.solovyev.android.checkout.Inventory
|
||||||
import org.solovyev.android.checkout.Purchase
|
import org.solovyev.android.checkout.Purchase
|
||||||
|
|
@ -145,12 +146,15 @@ class SubscriptionFragment : BaseFragment<FragmentSubscriptionBinding>(), GemPur
|
||||||
override fun setupCheckout() {
|
override fun setupCheckout() {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val subscriptions = purchaseHandler?.getAllSubscriptionProducts() ?: return@launch
|
val subscriptions = purchaseHandler?.getAllSubscriptionProducts() ?: return@launch
|
||||||
for (sku in subscriptions.skus) {
|
skus = subscriptions.skus
|
||||||
updateButtonLabel(sku, sku.price, subscriptions)
|
withContext(Dispatchers.Main) {
|
||||||
|
for (sku in subscriptions.skus) {
|
||||||
|
updateButtonLabel(sku, sku.price, subscriptions)
|
||||||
|
}
|
||||||
|
selectSubscription(PurchaseTypes.Subscription1Month)
|
||||||
|
hasLoadedSubscriptionOptions = true
|
||||||
|
updateSubscriptionInfo()
|
||||||
}
|
}
|
||||||
selectSubscription(PurchaseTypes.Subscription1Month)
|
|
||||||
hasLoadedSubscriptionOptions = true
|
|
||||||
updateSubscriptionInfo()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ class ChatFragment : BaseFragment<FragmentChatBinding>() {
|
||||||
viewModel?.updateUser("flags.communityGuidelinesAccepted", true)
|
viewModel?.updateUser("flags.communityGuidelinesAccepted", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel?.getUserData()?.observe(
|
viewModel?.user?.observe(
|
||||||
viewLifecycleOwner,
|
viewLifecycleOwner,
|
||||||
{
|
{
|
||||||
chatAdapter?.user = it
|
chatAdapter?.user = it
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,12 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
|
||||||
setReceivingUser(member.username, member.id)
|
setReceivingUser(member.username, member.id)
|
||||||
activity?.title = member.displayName
|
activity?.title = member.displayName
|
||||||
chatAdapter = InboxAdapter(user, member)
|
chatAdapter = InboxAdapter(user, member)
|
||||||
viewModel?.messages?.observe(this.viewLifecycleOwner, { chatAdapter?.submitList(it) })
|
viewModel?.messages?.observe(
|
||||||
|
this.viewLifecycleOwner
|
||||||
|
) {
|
||||||
|
markMessagesAsRead(it)
|
||||||
|
chatAdapter?.submitList(it)
|
||||||
|
}
|
||||||
|
|
||||||
binding?.recyclerView?.adapter = chatAdapter
|
binding?.recyclerView?.adapter = chatAdapter
|
||||||
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
|
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
|
||||||
|
|
@ -165,6 +170,10 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
|
||||||
component.inject(this)
|
component.inject(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun markMessagesAsRead(messages: List<ChatMessage>) {
|
||||||
|
socialRepository.markSomePrivateMessagesAsRead(user, messages)
|
||||||
|
}
|
||||||
|
|
||||||
private fun startAutoRefreshing() {
|
private fun startAutoRefreshing() {
|
||||||
if (refreshDisposable != null && refreshDisposable?.isDisposed != true) {
|
if (refreshDisposable != null && refreshDisposable?.isDisposed != true) {
|
||||||
refreshDisposable?.dispose()
|
refreshDisposable?.dispose()
|
||||||
|
|
|
||||||
|
|
@ -111,21 +111,22 @@ class GuildDetailFragment : BaseFragment<FragmentGuildDetailBinding>() {
|
||||||
private val sendInvitesResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
private val sendInvitesResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
if (it.resultCode == Activity.RESULT_OK) {
|
if (it.resultCode == Activity.RESULT_OK) {
|
||||||
val inviteData = HashMap<String, Any>()
|
val inviteData = HashMap<String, Any>()
|
||||||
inviteData["inviter"] = viewModel?.getUserData()?.value?.profile?.name ?: ""
|
inviteData["inviter"] = viewModel?.user?.value?.profile?.name ?: ""
|
||||||
if (it.data?.getBooleanExtra(GroupInviteActivity.IS_EMAIL_KEY, false) == true) {
|
val emails = it.data?.getStringArrayExtra(GroupInviteActivity.EMAILS_KEY)
|
||||||
val emails = it.data?.getStringArrayExtra(GroupInviteActivity.EMAILS_KEY)
|
if (emails != null && emails.isNotEmpty()) {
|
||||||
val invites = ArrayList<HashMap<String, String>>()
|
val invites = ArrayList<HashMap<String, String>>()
|
||||||
emails?.forEach { email ->
|
emails.forEach { email ->
|
||||||
val invite = HashMap<String, String>()
|
val invite = HashMap<String, String>()
|
||||||
invite["name"] = ""
|
invite["name"] = ""
|
||||||
invite["email"] = email
|
invite["email"] = email
|
||||||
invites.add(invite)
|
invites.add(invite)
|
||||||
}
|
}
|
||||||
inviteData["emails"] = invites
|
inviteData["emails"] = invites
|
||||||
} else {
|
}
|
||||||
val userIDs = it.data?.getStringArrayExtra(GroupInviteActivity.USER_IDS_KEY)
|
val userIDs = it.data?.getStringArrayExtra(GroupInviteActivity.USER_IDS_KEY)
|
||||||
val invites = mutableListOf<String>()
|
if (userIDs != null && userIDs.isNotEmpty()) {
|
||||||
userIDs?.forEach { invites.add(it) }
|
val invites = ArrayList<String>()
|
||||||
|
userIDs.forEach { invites.add(it) }
|
||||||
inviteData["usernames"] = invites
|
inviteData["usernames"] = invites
|
||||||
}
|
}
|
||||||
viewModel?.inviteToGroup(inviteData)
|
viewModel?.inviteToGroup(inviteData)
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel?.getGroupData()?.observe(viewLifecycleOwner, { updateParty(it) })
|
viewModel?.getGroupData()?.observe(viewLifecycleOwner, { updateParty(it) })
|
||||||
viewModel?.getUserData()?.observe(viewLifecycleOwner, { updateUser(it) })
|
viewModel?.user?.observe(viewLifecycleOwner, { updateUser(it) })
|
||||||
viewModel?.getMembersData()?.observe(viewLifecycleOwner, { updateMembersList(it) })
|
viewModel?.getMembersData()?.observe(viewLifecycleOwner, { updateMembersList(it) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -269,7 +269,7 @@ class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
|
||||||
}
|
}
|
||||||
) ?: return@forEachIndexed
|
) ?: return@forEachIndexed
|
||||||
val viewHolder = GroupMemberViewHolder(memberView)
|
val viewHolder = GroupMemberViewHolder(memberView)
|
||||||
viewHolder.bind(member, leaderID ?: "", viewModel?.getUserData()?.value?.id)
|
viewHolder.bind(member, leaderID ?: "", viewModel?.user?.value?.id)
|
||||||
viewHolder.onClickEvent = {
|
viewHolder.onClickEvent = {
|
||||||
FullProfileActivity.open(member.id ?: "")
|
FullProfileActivity.open(member.id ?: "")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication
|
||||||
import com.habitrpg.android.habitica.components.UserComponent
|
import com.habitrpg.android.habitica.components.UserComponent
|
||||||
import com.habitrpg.android.habitica.data.UserRepository
|
import com.habitrpg.android.habitica.data.UserRepository
|
||||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||||
|
import com.habitrpg.android.habitica.models.inventory.Equipment
|
||||||
import com.habitrpg.android.habitica.models.user.User
|
import com.habitrpg.android.habitica.models.user.User
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
|
|
@ -17,10 +18,13 @@ abstract class BaseViewModel(initializeComponent: Boolean = true) : ViewModel()
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var userRepository: UserRepository
|
lateinit var userRepository: UserRepository
|
||||||
|
|
||||||
private val user: MutableLiveData<User?> by lazy {
|
private val _user: MutableLiveData<User?> by lazy {
|
||||||
loadUserFromLocal()
|
loadUserFromLocal()
|
||||||
MutableLiveData<User?>()
|
MutableLiveData<User?>()
|
||||||
}
|
}
|
||||||
|
val user: LiveData<User?> by lazy {
|
||||||
|
_user
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (initializeComponent) {
|
if (initializeComponent) {
|
||||||
|
|
@ -38,13 +42,13 @@ abstract class BaseViewModel(initializeComponent: Boolean = true) : ViewModel()
|
||||||
|
|
||||||
internal val disposable = CompositeDisposable()
|
internal val disposable = CompositeDisposable()
|
||||||
|
|
||||||
fun getUserData(): LiveData<User?> = user
|
|
||||||
|
|
||||||
private fun loadUserFromLocal() {
|
private fun loadUserFromLocal() {
|
||||||
disposable.add(userRepository.getUser().observeOn(AndroidSchedulers.mainThread()).subscribe({ user.value = it }, RxErrorHandler.handleEmptyError()))
|
disposable.add(userRepository.getUser().observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe({ _user.value = it }, RxErrorHandler.handleEmptyError()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateUser(path: String, value: Any) {
|
fun updateUser(path: String, value: Any) {
|
||||||
disposable.add(userRepository.updateUser(path, value).subscribe({ }, RxErrorHandler.handleEmptyError()))
|
disposable.add(userRepository.updateUser(path, value)
|
||||||
|
.subscribe({ }, RxErrorHandler.handleEmptyError()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ open class NotificationsViewModel : BaseViewModel() {
|
||||||
var notifications = convertInvitationsToNotifications(it)
|
var notifications = convertInvitationsToNotifications(it)
|
||||||
if (it.flags?.newStuff == true) {
|
if (it.flags?.newStuff == true) {
|
||||||
val notification = Notification()
|
val notification = Notification()
|
||||||
notification.id = "new-stuff-notification"
|
notification.id = "custom-new-stuff-notification"
|
||||||
notification.type = Notification.Type.NEW_STUFF.type
|
notification.type = Notification.Type.NEW_STUFF.type
|
||||||
val data = NewStuffData()
|
val data = NewStuffData()
|
||||||
notification.data = data
|
notification.data = data
|
||||||
|
|
@ -179,11 +179,19 @@ open class NotificationsViewModel : BaseViewModel() {
|
||||||
* instead of one of the ones coming from server.
|
* instead of one of the ones coming from server.
|
||||||
*/
|
*/
|
||||||
private fun isCustomNotification(notification: Notification): Boolean {
|
private fun isCustomNotification(notification: Notification): Boolean {
|
||||||
return notification.id.startsWith("custom-") || notification.id == "new-stuff-notification"
|
return notification.id.startsWith("custom-")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isCustomNewStuffNotification(notification: Notification) =
|
||||||
|
notification.id == "custom-new-stuff-notification"
|
||||||
|
|
||||||
fun dismissNotification(notification: Notification) {
|
fun dismissNotification(notification: Notification) {
|
||||||
if (isCustomNotification(notification)) {
|
if (isCustomNotification(notification)) {
|
||||||
|
if (isCustomNewStuffNotification(notification)) {
|
||||||
|
customNotifications.onNext(
|
||||||
|
customNotifications.value?.filterNot { it.id == notification.id }
|
||||||
|
)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,6 +207,13 @@ open class NotificationsViewModel : BaseViewModel() {
|
||||||
.filter { !actionableNotificationTypes.contains(it.type) }
|
.filter { !actionableNotificationTypes.contains(it.type) }
|
||||||
.map { it.id }
|
.map { it.id }
|
||||||
|
|
||||||
|
val customNewStuffNotification = notifications
|
||||||
|
.firstOrNull { isCustomNewStuffNotification(it) }
|
||||||
|
|
||||||
|
if (customNewStuffNotification != null) {
|
||||||
|
dismissNotification(customNewStuffNotification)
|
||||||
|
}
|
||||||
|
|
||||||
if (dismissableIds.isEmpty()) {
|
if (dismissableIds.isEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ class PartyViewModel(initializeComponent: Boolean) : GroupViewModel(initializeCo
|
||||||
|
|
||||||
internal val isUserOnQuest: Boolean
|
internal val isUserOnQuest: Boolean
|
||||||
get() = !(
|
get() = !(
|
||||||
getGroupData().value?.quest?.members?.none { it.key == getUserData().value?.id }
|
getGroupData().value?.quest?.members?.none { it.key == user.value?.id }
|
||||||
?: true
|
?: true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -88,7 +88,7 @@ class PartyViewModel(initializeComponent: Boolean) : GroupViewModel(initializeCo
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showParticipantButtons(): Boolean {
|
fun showParticipantButtons(): Boolean {
|
||||||
val user = getUserData().value
|
val user = user.value
|
||||||
return !(user?.party == null || user.party?.quest == null) && !isQuestActive && user.party?.quest?.RSVPNeeded == true
|
return !(user?.party == null || user.party?.quest == null) && !isQuestActive && user.party?.quest?.RSVPNeeded == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import com.habitrpg.android.habitica.components.UserComponent
|
||||||
|
import com.habitrpg.android.habitica.data.InventoryRepository
|
||||||
|
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||||
|
import com.habitrpg.android.habitica.models.inventory.Equipment
|
||||||
|
import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class EquipmentOverviewViewModel(savedStateHandle: SavedStateHandle): BaseViewModel() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var inventoryRepository: InventoryRepository
|
||||||
|
|
||||||
|
override fun inject(component: UserComponent) {
|
||||||
|
component.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getGear(key: String, onSuccess: (Equipment) -> Unit) {
|
||||||
|
disposable.add(inventoryRepository.getEquipment(key).subscribe( {
|
||||||
|
onSuccess(it)
|
||||||
|
}, RxErrorHandler.handleEmptyError()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -312,12 +312,12 @@ open class HabiticaAlertDialog(context: Context) : AlertDialog(context, R.style.
|
||||||
|
|
||||||
private fun checkIfQueueAvailable(): Boolean {
|
private fun checkIfQueueAvailable(): Boolean {
|
||||||
val currentDialog = dialogQueue.firstOrNull() ?: return true
|
val currentDialog = dialogQueue.firstOrNull() ?: return true
|
||||||
if (currentDialog.isShowing) {
|
return if (currentDialog.isShowing && currentDialog.getActivity()?.isFinishing != true) {
|
||||||
return false
|
false
|
||||||
} else {
|
} else {
|
||||||
// The Dialog was probably dismissed in a weird way. Clear it out so that the queue doesn't get stuck
|
// The Dialog was probably dismissed in a weird way. Clear it out so that the queue doesn't get stuck
|
||||||
dialogQueue.removeAt(0)
|
dialogQueue.removeAt(0)
|
||||||
return true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ class QuestMenuView : LinearLayout {
|
||||||
orientation = VERTICAL
|
orientation = VERTICAL
|
||||||
|
|
||||||
binding.heartIconView.setImageBitmap(HabiticaIconsHelper.imageOfHeartDarkBg())
|
binding.heartIconView.setImageBitmap(HabiticaIconsHelper.imageOfHeartDarkBg())
|
||||||
|
binding.rageIconView.setImageBitmap(HabiticaIconsHelper.imageOfRage())
|
||||||
|
|
||||||
binding.pendingDamageIconView.setImageBitmap(HabiticaIconsHelper.imageOfDamage())
|
binding.pendingDamageIconView.setImageBitmap(HabiticaIconsHelper.imageOfDamage())
|
||||||
|
|
||||||
|
|
@ -50,16 +51,21 @@ class QuestMenuView : LinearLayout {
|
||||||
|
|
||||||
fun configure(quest: Quest) {
|
fun configure(quest: Quest) {
|
||||||
binding.healthBarView.setCurrentValue(quest.progress?.hp ?: 0.0)
|
binding.healthBarView.setCurrentValue(quest.progress?.hp ?: 0.0)
|
||||||
|
binding.rageBarView.setCurrentValue(quest.progress?.rage ?: 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun configure(questContent: QuestContent) {
|
fun configure(questContent: QuestContent) {
|
||||||
this.questContent = questContent
|
this.questContent = questContent
|
||||||
binding.healthBarView.setMaxValue(questContent.boss?.hp?.toDouble() ?: 0.0)
|
binding.healthBarView.setMaxValue(questContent.boss?.hp?.toDouble() ?: 0.0)
|
||||||
binding.bottomView.setBackgroundColor(questContent.colors?.darkColor ?: 0)
|
|
||||||
//binding.bossArtView.setBackgroundColor(questContent.colors?.mediumColor ?: 0)
|
|
||||||
//DataBindingUtils.loadImage(binding.bossArtView, "quest_" + questContent.key)
|
|
||||||
binding.bossNameView.text = questContent.boss?.name
|
binding.bossNameView.text = questContent.boss?.name
|
||||||
binding.typeTextView.text = context.getString(R.string.boss_quest)
|
binding.typeTextView.text = context.getString(R.string.boss_quest)
|
||||||
|
|
||||||
|
if (questContent.boss?.hasRage == true) {
|
||||||
|
binding.rageView.visibility = View.VISIBLE
|
||||||
|
binding.rageBarView.setMaxValue(questContent.boss?.rage?.value ?: 0.0)
|
||||||
|
} else {
|
||||||
|
binding.rageView.visibility = View.GONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun configure(user: User) {
|
fun configure(user: User) {
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,7 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
|
||||||
if (editedTags.containsKey(tag.id)) {
|
if (editedTags.containsKey(tag.id)) {
|
||||||
editedTags.remove(tag.id)
|
editedTags.remove(tag.id)
|
||||||
}
|
}
|
||||||
|
activeTags.remove(tag.id)
|
||||||
tags.remove(tag)
|
tags.remove(tag)
|
||||||
tagsList.removeView(wrapper)
|
tagsList.removeView(wrapper)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,12 @@ class ReminderContainer @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
var firstDayOfWeek: Int? = null
|
var firstDayOfWeek: Int? = null
|
||||||
|
set(value) {
|
||||||
|
children
|
||||||
|
.filterIsInstance<ReminderItemFormView>()
|
||||||
|
.forEach { it.firstDayOfWeek = value }
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
orientation = VERTICAL
|
orientation = VERTICAL
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
We’ve improved more of our notifications so they bring you to the relevant screen when tapped. When customizing your avatar, you’ll be able to see your avatar updating in real time as you select different options. You can also see your current Gems when gifting Gems from your balance now. We’ve fixed a few bugs too, including one where tasks wouldn’t load when the app is left running in the background, the stats widget works again, and another where Dailies wouldn’t filter properly on launch.
|
In this update we’ve fixed some bugs, added more seasonal event support, and made a few quality of life improvements! Sending Guild and Party invites through email should work more reliably. We’ve improved task drag and drop logic when filters are applied to make the order more consistent. Habit streak is now referred to as ‘Habit Counter’ to better reflect the actual behavior. Issues with subscriptions not cancelling fully or getting errors when buying multiple Gem packages should be resolved.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue