Merge branch '3.5.2' into Fixes#1475

This commit is contained in:
Phillip Thelen 2022-03-18 11:49:06 +01:00 committed by GitHub
commit bfd7323813
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
79 changed files with 577 additions and 366 deletions

View file

@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

View file

@ -51,10 +51,10 @@ dependencies {
compileOnly 'javax.annotation:javax.annotation-api:1.3.2'
compileOnly 'com.github.pengrad:jdk9-deps:1.0'
//App Compatibility and Material Design
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation "androidx.preference:preference-ktx:1.1.1"
implementation "androidx.preference:preference-ktx:1.2.0"
// Markdown
@ -121,10 +121,10 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.4.0"
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.4.0'
implementation 'com.plattysoft.leonids:LeonidsLib:1.3.2'
implementation "androidx.fragment:fragment-ktx:1.4.0"
implementation "androidx.fragment:fragment-ktx:1.4.1"
implementation "androidx.paging:paging-runtime-ktx:3.1.0"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
@ -142,7 +142,7 @@ dependencies {
android {
compileSdkVersion 32
buildToolsVersion '30.0.2'
buildToolsVersion '30.0.3'
testOptions {
unitTests {
includeAndroidResources = true
@ -157,7 +157,7 @@ android {
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"
versionCode 3218
versionCode 3223
versionName "3.5"
targetSdkVersion 32
@ -382,8 +382,8 @@ def mainSrc = "${project.projectDir}/src/main/java"
task jacocoTestReport(type: JacocoReport, dependsOn: 'testProdDebugUnitTest') {
group = "Reporting"
reports {
html.enabled = true
xml.enabled = true
html.required.set(true)
xml.required.set(true)
//html.destination = "${buildDir}/reports/jacoco"
}
@ -404,7 +404,6 @@ task jacocoTestReport(type: JacocoReport, dependsOn: 'testProdDebugUnitTest') {
task ktlint(type: JavaExec, group: "verification") {
description = "Check Kotlin code style."
classpath = configurations.ktlint
main = "com.pinterest.ktlint.Main"
args "--disabled_rules=no-wildcard-imports", "--reporter=plain?group_by_file", "src/**/*.kt"
// to generate report in checkstyle format prepend following args:
// "--reporter=plain", "--reporter=checkstyle,output=${buildDir}/ktlint.xml"
@ -417,6 +416,5 @@ check.dependsOn ktlint
task ktlintFormat(type: JavaExec, group: "formatting") {
description = "Fix Kotlin code style deviations."
classpath = configurations.ktlint
main = "com.pinterest.ktlint.Main"
args "--disabled_rules=no-wildcard-imports", "-F", "src/**/*.kt"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 893 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 694 B

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 958 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -233,11 +233,12 @@
android:layout_width="wrap_content"
android:paddingStart="41dp"
android:paddingEnd="41dp"
android:paddingBottom="20dp"
android:paddingBottom="30dp"
android:paddingTop="70dp"
android:text="@string/gem_purchase_title"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:lineSpacingExtra="3dp"
android:textColor="@color/white"
style="@style/Caption2"
android:drawablePadding="@dimen/spacing_medium"/>

View file

@ -17,19 +17,37 @@
android:contentDescription="@string/gems"
android:layout_marginTop="20dp"/>
<com.habitrpg.android.habitica.ui.views.DayNightTextView
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/gem_amount"
app:dayTextColor="@color/white"
app:nightTextColor="@color/white"
android:gravity="center"
android:layout_gravity="center"
android:textSize="16sp"
android:letterSpacing="0.027"
android:fontFamily="@string/font_family_medium"
android:layout_margin="10dp"
tools:text="21 GEMS" />
android:orientation="horizontal">
<com.habitrpg.android.habitica.ui.views.DayNightTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/gem_amount"
app:dayTextColor="@color/white"
app:nightTextColor="@color/white"
android:gravity="center"
android:layout_gravity="center"
android:textSize="16sp"
android:letterSpacing="0.03"
android:fontFamily="@string/font_family_medium"
android:layout_marginVertical="10dp"
android:layout_marginEnd="2dp"
tools:text="21" />
<com.habitrpg.android.habitica.ui.views.DayNightTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:dayTextColor="@color/white"
app:nightTextColor="@color/white"
android:gravity="center"
android:layout_gravity="center"
android:textSize="16sp"
android:letterSpacing="0.03"
android:layout_marginVertical="10dp"
android:textAllCaps="true"
android:text="@string/gems" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
@ -38,7 +56,7 @@
android:background="@drawable/purchase_button_background"
android:textColor="@color/brand_400"
android:gravity="center"
android:textSize="16sp"
style="@style/Subheader1"
android:letterSpacing="0.02"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"

View file

@ -464,7 +464,7 @@
<argument
android:name="url"
app:argType="string" />
<deepLink app:uri="habitica.com/promo/web" />
<deepLink app:uri="habitica.com/promo/web?url={url}" />
</fragment>
<fragment
android:id="@+id/teamBoardFragment"

View file

@ -143,6 +143,7 @@
<string name="rewards">Rewards</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="discard">Discard</string>
<string name="quest">Quest</string>
<string name="avatar_body">Body</string>
<string name="avatar_hair">Hair</string>
@ -378,7 +379,7 @@
<string name="google_services_missing">Google play services could not be found.</string>
<string name="gem_purchase_toolbartitle">Purchase</string>
<string name="gem_purchase_title">Buying gems supports the developers and helps keep Habitica running</string>
<string name="gem_purchase_subtitle">Gems allow you to buy fun extras for your account:</string>
<string name="gem_purchase_subtitle">Gems allow you to buy fun extras for your account</string>
<string name="gem_purchase_listitem1">Cool costumes for your avatar</string>
<string name="gem_purchase_listitem2">Awesome backgrounds</string>
<string name="gem_purchase_listitem3">Quests that reward you with pet eggs</string>
@ -470,6 +471,7 @@
<string name="myster_item_notes">Each month, subscribers will receive a mystery item. This is usually released about one week before the end of the month.</string>
<string name="open">Open</string>>
<string name="is_open">Open</string>
<string name="use_item">Use Item on party member</string>>
<string name="checkInRewardEarned">You earned a %1$s as a reward for your devotion to improving your life.</string>
<string name="nextPrizeUnlocks" tools:ignore="PluralsCandidate">Next prize in %1$d Check-Ins</string>
<string name="pending_approval">pending approval</string>
@ -527,6 +529,7 @@
<string name="no_billing_gems">Your device does not have any of the supported payment methods. Please use the habitica website if you want to purchase gems.</string>
<string name="no_billing_subscriptions">Your device does not have any of the supported payment methods. Please use the habitica website if you want to purchase a subscription.</string>
<string name="save">Save</string>
<string name="unsaved_changes">Unsaved Changes</string>
<string name="location">Location</string>
<string name="gem_reward">Gem prize</string>
<string name="tasks">Tasks</string>
@ -551,6 +554,7 @@
<string name="start_day">Start My Day</string>
<string name="leave_party_confirmation">Are you sure you want to leave this party?</string>
<string name="leave_party_challenges_confirmation">Do you want to keep participating in the Challenge when leaving the Party?</string>
<string name="discard_changes_to_task_message">Are you sure you want to discard changes to this task?</string>
<string name="rejoin_party">You will not be able to rejoin this party unless invited.</string>
<string name="pending">Pending</string>
<string name="accepted">Accepted</string>

View file

@ -103,6 +103,7 @@ import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.InboxViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.MainActivityViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment.EquipmentOverviewViewModel;
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog;
@ -352,4 +353,6 @@ public interface UserComponent {
void inject(@NotNull MainActivityViewModel mainActivityViewModel);
void inject(@NotNull GuidelinesActivity guidelinesActivity);
void inject(@NotNull MainUserViewModel mainUserViewModel);
}

View file

@ -84,6 +84,5 @@ interface SocialRepository : BaseRepository {
fun getGroupMembership(id: String): Flowable<GroupMembership>
fun getGroupMemberships(): Flowable<out List<GroupMembership>>
fun getChatmessage(messageID: String): Flowable<ChatMessage>
fun blockMember(userID: String): Flowable<List<String>>
}

View file

@ -9,7 +9,6 @@ import com.habitrpg.android.habitica.models.ContentResult
import com.habitrpg.android.habitica.models.WorldState
import com.habitrpg.android.habitica.models.inventory.SpecialItem
import io.reactivex.rxjava3.core.Flowable
import io.realm.RealmList
import java.util.*
class ContentRepositoryImpl<T : ContentLocalRepository>(localRepository: T, apiClient: ApiClient, context: Context) : BaseRepositoryImpl<T>(localRepository, apiClient), ContentRepository {
@ -24,7 +23,6 @@ class ContentRepositoryImpl<T : ContentLocalRepository>(localRepository: T, apiC
return if (forced || now - this.lastContentSync > 300000) {
lastContentSync = now
apiClient.content.doOnNext {
it.special = RealmList()
it.special.add(mysteryItem)
localRepository.saveContent(it)
}

View file

@ -37,10 +37,6 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
}
}
override fun getChatmessage(messageID: String): Flowable<ChatMessage> {
return localRepository.getChatMessage(messageID)
}
override fun blockMember(userID: String): Flowable<List<String>> {
return apiClient.blockMember(userID)
}
@ -92,7 +88,9 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
return Flowable.empty()
}
val liked = chatMessage.userLikesMessage(userID)
localRepository.likeMessage(chatMessage, userID, !liked)
if (chatMessage.isManaged) {
localRepository.likeMessage(chatMessage, userID, !liked)
}
return apiClient.likeMessage(chatMessage.groupId ?: "", chatMessage.id)
}

View file

@ -47,5 +47,4 @@ interface SocialLocalRepository : BaseLocalRepository {
fun saveGroupMemberships(userID: String?, memberships: List<GroupMembership>)
fun saveInboxMessages(userID: String, recipientID: String, messages: List<ChatMessage>, page: Int)
fun saveInboxConversations(userID: String, conversations: List<InboxConversation>)
fun getChatMessage(messageID: String): Flowable<ChatMessage>
}

View file

@ -28,6 +28,7 @@ open class RealmContentLocalRepository(realm: Realm) : RealmBaseLocalRepository(
realm1.insertOrUpdate(contentResult.mounts)
realm1.insertOrUpdate(contentResult.spells)
realm1.insertOrUpdate(contentResult.special)
realm1.insertOrUpdate(contentResult.appearances)
realm1.insertOrUpdate(contentResult.backgrounds)
realm1.insertOrUpdate(contentResult.faq)

View file

@ -96,7 +96,7 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
"hatchingPotions" -> it.items?.hatchingPotions
"food" -> it.items?.food
"quests" -> it.items?.quests
"special" -> it.items?.special?.ownedItems
"special" -> it.items?.special
else -> emptyList()
} ?: emptyList()
if (includeZero) {
@ -286,7 +286,7 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
return
}
val liveUser = getLiveObject(user)
val ownedItems = liveUser?.items?.special?.ownedItems
val ownedItems = liveUser?.items?.special
val item = ownedItems?.firstOrNull() { it.key == "inventory_present" }
executeTransaction {
if (item != null && item.isValid) {

View file

@ -12,14 +12,6 @@ import io.realm.Sort
import java.util.*
class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), SocialLocalRepository {
override fun getChatMessage(messageID: String): Flowable<ChatMessage> = RxJavaBridge.toV3Flowable(
realm.where(ChatMessage::class.java)
.equalTo("id", messageID)
.findAll()
.asFlowable()
.filter { it.isLoaded && it.isNotEmpty() }
.map { it.first() }
)
override fun getGroupMembership(userId: String, id: String): Flowable<GroupMembership> = RxJavaBridge.toV3Flowable(
realm.where(GroupMembership::class.java)

View file

@ -146,22 +146,10 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
override fun getSpecialItems(user: User): Flowable<out List<Skill>> {
val specialItems = user.items?.special
val ownedItems = ArrayList<String>()
if (specialItems != null) {
if (specialItems.snowball > 0) {
ownedItems.add("snowball")
for (key in listOf("snowball", "shinySeed", "seafoam", "spookySparkles")) {
if (specialItems?.firstOrNull() { it.key == key }?.numberOwned ?: 0 > 0) {
ownedItems.add(key)
}
if (specialItems.shinySeed > 0) {
ownedItems.add("shinySeed")
}
if (specialItems.seafoam > 0) {
ownedItems.add("seafoam")
}
if (specialItems.spookySparkles > 0) {
ownedItems.add("spookySparkles")
}
}
if (ownedItems.size == 0) {
ownedItems.add("")
}
return RxJavaBridge.toV3Flowable(
realm.where(Skill::class.java)

View file

@ -1,6 +1,7 @@
package com.habitrpg.android.habitica.helpers
import android.app.AlarmManager
import android.app.AlarmManager.AlarmClockInfo
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
@ -121,6 +122,7 @@ class TaskAlarmManager(private var context: Context, private var taskRepository:
const val TASK_NAME_INTENT_KEY = "TASK_NAME"
fun scheduleDailyReminder(context: Context?) {
if (context == null) return
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
if (prefs.getBoolean("use_reminder", false)) {
val timeval = prefs.getString("reminder_time", "19:00")
@ -174,7 +176,7 @@ class TaskAlarmManager(private var context: Context, private var taskRepository:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
alarmManager?.setWindow(AlarmManager.RTC_WAKEUP, time, 60000, pendingIntent)
} else {
alarmManager?.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pendingIntent)
alarmManager?.setAlarmClock(AlarmClockInfo(time, pendingIntent), pendingIntent)
}
}
}

View file

@ -15,6 +15,7 @@ open class SpecialItem : RealmObject(), Item {
internal var notes: String = ""
override var value: Int = 0
override var event: ItemEvent? = null
var target: String? = null
var isMysteryItem: Boolean = false
companion object {

View file

@ -256,6 +256,47 @@ open class Task : RealmObject, BaseMainObject, Parcelable {
}
}
fun isBeingEdited(task: Task): Boolean {
when {
text != task.text -> return true
notes != task.notes -> return true
reminders != task.reminders -> return true
checklist != task.checklist -> return true
priority != task.priority -> return true
attribute != task.attribute && attribute != null -> return true
tags != task.tags -> return true
}
if (type == TaskType.HABIT) {
return when {
up != task.up -> true
down != task.down -> true
frequency != task.frequency -> true
counterUp != task.counterUp -> true
counterDown != task.counterDown -> true
else -> false
}
} else if (type == TaskType.DAILY) {
return when {
startDate != task.startDate -> true
everyX != task.everyX -> true
frequency != task.frequency -> true
repeat != task.repeat -> true
streak != task.streak -> true
else -> false
}
} else if (type == TaskType.TODO) {
return dueDate != task.dueDate
} else if (type == TaskType.REWARD) {
return value != task.value
} else {
return false
}
}
override fun hashCode(): Int {
return id?.hashCode() ?: 0
}

View file

@ -13,6 +13,7 @@ open class Items : RealmObject, BaseObject {
eggs?.forEach { it.itemType = "eggs" }
food?.forEach { it.itemType = "food" }
quests?.forEach { it.itemType = "quests" }
special?.forEach { it.itemType = "special" }
}
var eggs: RealmList<OwnedItem>? = null
@ -35,6 +36,12 @@ open class Items : RealmObject, BaseObject {
field = value
field?.forEach { it.itemType = "quests" }
}
var special: RealmList<OwnedItem>? = null
set(value) {
field = value
field?.forEach { it.itemType = "special" }
}
var pets: RealmList<OwnedPet>? = null
var mounts: RealmList<OwnedMount>? = null
var currentMount: String? = null
@ -44,7 +51,6 @@ open class Items : RealmObject, BaseObject {
// private QuestContent quest;
var gear: Gear? = null
var special: SpecialItems? = null
constructor(currentMount: String, currentPet: String, lastDropCount: Int, lastDropDate: Date) {
this.currentMount = currentMount
@ -54,4 +60,14 @@ open class Items : RealmObject, BaseObject {
}
constructor()
val hasTransformationItems: Boolean
get() {
return special?.any { transformationItem ->
transformationItem.key == ("seafoam") && transformationItem.numberOwned > 0||
transformationItem.key == ("shinySeed") && transformationItem.numberOwned > 0||
transformationItem.key == ("snowball") && transformationItem.numberOwned > 0||
transformationItem.key == ("spookySparkles") && transformationItem.numberOwned > 0
} ?: false
}
}

View file

@ -1,20 +0,0 @@
package com.habitrpg.android.habitica.models.user
import com.habitrpg.android.habitica.models.BaseObject
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.RealmClass
@RealmClass(embedded = true)
open class SpecialItems : RealmObject(), BaseObject {
var ownedItems: RealmList<OwnedItem>? = null
var seafoam: Int = 0
var shinySeed: Int = 0
var snowball: Int = 0
var spookySparkles: Int = 0
val hasSpecialItems: Boolean
get() {
return seafoam > 0 || shinySeed > 0 || snowball > 0 || spookySparkles > 0
}
}

View file

@ -34,7 +34,7 @@ class AppModule(private val application: Application) {
@Provides
@Singleton
fun provideSharedPreferences(context: Context?): SharedPreferences {
fun provideSharedPreferences(context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context)
}

View file

@ -4,8 +4,10 @@ import android.content.Context;
import android.content.SharedPreferences;
import com.habitrpg.android.habitica.data.TaskRepository;
import com.habitrpg.android.habitica.data.UserRepository;
import com.habitrpg.android.habitica.helpers.TaskAlarmManager;
import com.habitrpg.android.habitica.helpers.UserScope;
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel;
import javax.inject.Named;
@ -28,4 +30,10 @@ public class UserModule {
public String providesUserID(SharedPreferences sharedPreferences) {
return sharedPreferences.getString("UserID", "");
}
@Provides
@UserScope
MainUserViewModel providesUserViewModel(UserRepository userRepository) {
return new MainUserViewModel(userRepository);
}
}

View file

@ -7,7 +7,7 @@ import androidx.preference.DialogPreference
import java.text.DateFormat
import java.util.*
class TimePreference(ctxt: Context?, attrs: AttributeSet?) : DialogPreference(ctxt, attrs) {
class TimePreference(ctxt: Context, attrs: AttributeSet?) : DialogPreference(ctxt, attrs) {
private var timeval: String? = null
override fun onGetDefaultValue(a: TypedArray, index: Int): Any {
return a.getString(index)!!
@ -39,7 +39,7 @@ class TimePreference(ctxt: Context?, attrs: AttributeSet?) : DialogPreference(ct
}
}
override fun setSummary(summary: CharSequence) {
override fun setSummary(summary: CharSequence?) {
val calendar = Calendar.getInstance(Locale.getDefault())
calendar.set(Calendar.HOUR_OF_DAY, getHour(timeval))
calendar.set(Calendar.MINUTE, getMinute(timeval))

View file

@ -73,9 +73,9 @@ class AvatarWithBarsViewModel(private val context: Context, private val binding:
if (!stats.isBuffed) {
binding.buffImageView.visibility = View.GONE
}
binding.currencyView.gold = stats.gp ?: 0.0
if (user is User) {
binding.currencyView.gold = stats.gp ?: 0.0
binding.currencyView.hourglasses = user.hourglassCount.toDouble()
binding.currencyView.gems = user.gemCount.toDouble()
}

View file

@ -20,7 +20,7 @@ class GemPurchaseOptionsView(context: Context, attrs: AttributeSet) : FrameLayou
0, 0
)
binding.gemAmount.text = "${a.getText(R.styleable.GemPurchaseOptionsView_gemAmount)} GEMS"
binding.gemAmount.text = a.getText(R.styleable.GemPurchaseOptionsView_gemAmount)
val iconRes = a.getDrawable(R.styleable.GemPurchaseOptionsView_gemDrawable)
if (iconRes != null) {

View file

@ -273,7 +273,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack
navigationController.addOnDestinationChangedListener { _, destination, arguments -> updateToolbarTitle(destination, arguments) }
if (launchScreen == "/party") {
if (viewModel.isUserInParty) {
if (viewModel.userViewModel.isUserInParty) {
MainNavigationController.navigate(R.id.partyFragment)
}
}
@ -430,7 +430,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack
}
private fun displayDeathDialogIfNeeded() {
if (!viewModel.isUserFainted) {
if (!viewModel.userViewModel.isUserFainted) {
return
}

View file

@ -1,6 +1,7 @@
package com.habitrpg.android.habitica.ui.activities
import android.app.Activity
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.content.res.ColorStateList
@ -13,6 +14,7 @@ import android.widget.CheckBox
import android.widget.TextView
import androidx.appcompat.widget.AppCompatCheckBox
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.view.children
import androidx.core.view.forEachIndexed
import androidx.core.widget.NestedScrollView
@ -21,7 +23,6 @@ import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ChallengeRepository
import com.habitrpg.android.habitica.data.TagRepository
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.ActivityTaskFormBinding
import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher
import com.habitrpg.android.habitica.extensions.addCancelButton
@ -64,6 +65,7 @@ class TaskFormActivity : BaseActivity() {
private var isChallengeTask = false
private var usesTaskAttributeStats = false
private var task: Task? = null
private var initialTaskInstance: Task? = null
private var taskType: TaskType = TaskType.HABIT
private var tags = listOf<Tag>()
private var preselectedTags: ArrayList<String>? = null
@ -74,6 +76,7 @@ class TaskFormActivity : BaseActivity() {
setSelectedAttribute(value)
}
private var isDiscardCancelled: Boolean = false
private var canSave: Boolean = false
private var tintColor: Int = 0
@ -198,6 +201,7 @@ class TaskFormActivity : BaseActivity() {
{
if (!it.isValid) return@subscribe
task = it
initialTaskInstance = it
// tintColor = ContextCompat.getColor(this, it.mediumTaskColor)
fillForm(it)
it.challengeID?.let { challengeID ->
@ -224,17 +228,21 @@ class TaskFormActivity : BaseActivity() {
task = bundle.getParcelable(PARCELABLE_TASK)
task?.let { fillForm(it) }
}
else -> title = getString(
R.string.create_task,
getString(
when (taskType) {
TaskType.DAILY -> R.string.daily
TaskType.TODO -> R.string.todo
TaskType.REWARD -> R.string.reward
else -> R.string.habit
}
else -> {
title = getString(
R.string.create_task,
getString(
when (taskType) {
TaskType.DAILY -> R.string.daily
TaskType.TODO -> R.string.todo
TaskType.REWARD -> R.string.reward
else -> R.string.habit
}
)
)
)
initialTaskInstance = configureTask(Task())
}
}
configureForm()
}
@ -252,6 +260,29 @@ class TaskFormActivity : BaseActivity() {
}
}
override fun onBackPressed() {
val currentTaskInstance = configureTask(Task())
if (initialTaskInstance?.isBeingEdited(currentTaskInstance) == true) {
val alert = HabiticaAlertDialog(this)
alert.setTitle(R.string.unsaved_changes)
alert.setMessage(R.string.discard_changes_to_task_message)
alert.addButton(R.string.discard, true, true) { _, _ ->
analyticsManager.logEvent("discard_task", bundleOf(Pair("is_creating", isCreating)))
super.onBackPressed()
}
alert.addButton(R.string.cancel, false) { _, _ ->
isDiscardCancelled = true
alert.dismiss()
}
alert.setOnDismissListener(DialogInterface.OnDismissListener {
isDiscardCancelled = true
})
alert.show()
} else {
super.onBackPressed()
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
if (isCreating) {
menuInflater.inflate(R.menu.menu_task_create, menu)
@ -437,19 +468,10 @@ class TaskFormActivity : BaseActivity() {
}
}
private fun saveTask() {
if (isSaving) {
return
}
isSaving = true
var thisTask = task
if (thisTask == null) {
thisTask = Task()
thisTask.type = taskType
thisTask.dateCreated = Date()
} else {
if (!thisTask.isValid) return
}
private fun configureTask(thisTask: Task): Task {
thisTask.type = taskType
thisTask.dateCreated = Date()
thisTask.text = binding.textEditText.text.toString()
thisTask.notes = binding.notesEditText.text.toString()
thisTask.priority = binding.taskDifficultyButtons.selectedDifficulty
@ -460,8 +482,10 @@ class TaskFormActivity : BaseActivity() {
thisTask.up = binding.habitScoringButtons.isPositive
thisTask.down = binding.habitScoringButtons.isNegative
thisTask.frequency = binding.habitResetStreakButtons.selectedResetOption.value
if (binding.habitAdjustPositiveStreakView.text?.isNotEmpty() == true) thisTask.counterUp = binding.habitAdjustPositiveStreakView.text.toString().toIntCatchOverflow()
if (binding.habitAdjustNegativeStreakView.text?.isNotEmpty() == true) thisTask.counterDown = binding.habitAdjustNegativeStreakView.text.toString().toIntCatchOverflow()
if (binding.habitAdjustPositiveStreakView.text?.isNotEmpty() == true) thisTask.counterUp =
binding.habitAdjustPositiveStreakView.text.toString().toIntCatchOverflow()
if (binding.habitAdjustNegativeStreakView.text?.isNotEmpty() == true) thisTask.counterDown =
binding.habitAdjustNegativeStreakView.text.toString().toIntCatchOverflow()
} else if (taskType == TaskType.DAILY) {
thisTask.startDate = binding.taskSchedulingControls.startDate
thisTask.everyX = binding.taskSchedulingControls.everyX
@ -469,15 +493,13 @@ class TaskFormActivity : BaseActivity() {
thisTask.repeat = binding.taskSchedulingControls.weeklyRepeat
thisTask.setDaysOfMonth(binding.taskSchedulingControls.daysOfMonth)
thisTask.setWeeksOfMonth(binding.taskSchedulingControls.weeksOfMonth)
if (binding.habitAdjustPositiveStreakView.text?.isNotEmpty() == true) thisTask.streak = binding.habitAdjustPositiveStreakView.text.toString().toIntCatchOverflow()
if (binding.habitAdjustPositiveStreakView.text?.isNotEmpty() == true) thisTask.streak =
binding.habitAdjustPositiveStreakView.text.toString().toIntCatchOverflow()
} else if (taskType == TaskType.TODO) {
thisTask.dueDate = binding.taskSchedulingControls.dueDate
} else if (taskType == TaskType.REWARD) {
thisTask.value = binding.rewardValue.value
}
val resultIntent = Intent()
resultIntent.putExtra(TASK_TYPE_KEY, taskType)
if (!isChallengeTask) {
if (taskType == TaskType.DAILY || taskType == TaskType.TODO) {
thisTask.checklist = RealmList()
@ -492,10 +514,40 @@ class TaskFormActivity : BaseActivity() {
thisTask.tags?.add(tags[index])
}
}
}
return thisTask
}
private fun saveTask() {
if (isSaving) {
return
}
isSaving = true
var thisTask = task
if (thisTask == null) {
thisTask = Task()
thisTask.type = taskType
thisTask.dateCreated = Date()
} else {
if (!thisTask.isValid) return
}
thisTask = configureTask(thisTask)
val resultIntent = Intent()
resultIntent.putExtra(TASK_TYPE_KEY, taskType)
if (!isChallengeTask) {
if (isCreating) {
if (isDiscardCancelled){
analyticsManager.logEvent("back_to_task", bundleOf(Pair("is_creating", isCreating)))
}
taskRepository.createTaskInBackground(thisTask)
} else {
if (isDiscardCancelled){
analyticsManager.logEvent("back_to_task", bundleOf(Pair("is_creating", isCreating)))
}
taskRepository.updateTaskInBackground(thisTask)
}

View file

@ -12,12 +12,13 @@ import com.habitrpg.android.habitica.databinding.SkillListItemBinding
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.extensions.isUsingNightModeResources
import com.habitrpg.android.habitica.models.Skill
import com.habitrpg.android.habitica.models.user.SpecialItems
import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
import io.realm.RealmList
class SkillsRecyclerViewAdapter : RecyclerView.Adapter<SkillsRecyclerViewAdapter.SkillViewHolder>() {
@ -34,7 +35,7 @@ class SkillsRecyclerViewAdapter : RecyclerView.Adapter<SkillsRecyclerViewAdapter
field = value
notifyDataSetChanged()
}
var specialItems: SpecialItems? = null
var specialItems: RealmList<OwnedItem>?? = null
set(value) {
field = value
notifyDataSetChanged()
@ -134,13 +135,7 @@ class SkillsRecyclerViewAdapter : RecyclerView.Adapter<SkillsRecyclerViewAdapter
}
private fun getOwnedCount(key: String): Int {
return when (key) {
"snowball" -> specialItems?.snowball
"shinySeed" -> specialItems?.shinySeed
"seafoam" -> specialItems?.seafoam
"spookySparkles" -> specialItems?.spookySparkles
else -> 0
} ?: 0
return specialItems?.firstOrNull() { it.key == key }?.numberOwned ?: 0
}
}
}

View file

@ -47,6 +47,7 @@ class ItemRecyclerAdapter(val context: Context, val user: User?) : BaseRecyclerV
private val hatchPetSubject = PublishSubject.create<Pair<HatchingPotion, Egg>>()
private val feedPetSubject = PublishSubject.create<Food>()
private val createNewPartySubject = PublishSubject.create<Boolean>()
private val useSpecialSubject = PublishSubject.create<SpecialItem>()
fun getSellItemFlowable(): Flowable<OwnedItem> {
return sellItemEvents.toFlowable(BackpressureStrategy.DROP)
@ -63,6 +64,7 @@ class ItemRecyclerAdapter(val context: Context, val user: User?) : BaseRecyclerV
val hatchPetEvents: Flowable<Pair<HatchingPotion, Egg>> = hatchPetSubject.toFlowable(BackpressureStrategy.DROP)
val feedPetEvents: Flowable<Food> = feedPetSubject.toFlowable(BackpressureStrategy.DROP)
val startNewPartyEvents: Flowable<Boolean> = createNewPartySubject.toFlowable(BackpressureStrategy.DROP)
val useSpecialEvents: Flowable<SpecialItem> = useSpecialSubject.toFlowable(BackpressureStrategy.DROP)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
return ItemViewHolder(ItemItemBinding.inflate(context.layoutInflater, parent, false))
@ -115,9 +117,14 @@ class ItemRecyclerAdapter(val context: Context, val user: User?) : BaseRecyclerV
if (item is QuestContent) {
imageName = "inventory_quest_scroll_" + ownedItem.key
} else if (item is SpecialItem) {
val sdf = SimpleDateFormat("MM", Locale.getDefault())
val month = sdf.format(Date())
imageName = "inventory_present_$month"
if (item.key == "inventory_present") {
val sdf = SimpleDateFormat("MM", Locale.getDefault())
val month = sdf.format(Date())
imageName = "inventory_present_$month"
} else {
imageName = "shop_" + ownedItem.key
}
} else {
val type = when (ownedItem.itemType) {
"eggs" -> "Egg"
@ -164,7 +171,10 @@ class ItemRecyclerAdapter(val context: Context, val user: User?) : BaseRecyclerV
val specialItem = item as SpecialItem
if (specialItem.isMysteryItem && ownedItem?.numberOwned ?: 0 > 0) {
menu.addMenuItem(BottomSheetMenuItem(resources.getString(R.string.open)))
} else if (ownedItem?.numberOwned ?: 0 > 0){
menu.addMenuItem(BottomSheetMenuItem(resources.getString(R.string.use_item)))
}
}
menu.setSelectionRunnable { index ->
item?.let { selectedItem ->
@ -189,7 +199,12 @@ class ItemRecyclerAdapter(val context: Context, val user: User?) : BaseRecyclerV
}
}
is SpecialItem -> openMysteryItemEvents.onNext(selectedItem)
is SpecialItem ->
if (item?.key != "inventory_present") {
useSpecialSubject.onNext(selectedItem)
} else {
openMysteryItemEvents.onNext(selectedItem)
}
}
}
}

View file

@ -15,7 +15,11 @@ class MountDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Ada
private var ownedMounts: Map<String, OwnedMount>? = null
private val equipEvents = PublishSubject.create<String>()
private var user: User? = null
var currentMount: String? = null
set(value) {
field = value
notifyDataSetChanged()
}
private var itemList: List<Any> = ArrayList()
@ -37,7 +41,7 @@ class MountDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Ada
override fun onBindViewHolder(holder: androidx.recyclerview.widget.RecyclerView.ViewHolder, position: Int) {
when (val obj = this.itemList[position]) {
is StableSection -> (holder as? SectionViewHolder)?.bind(obj)
is Mount -> (holder as? MountViewHolder)?.bind(obj, ownedMounts?.get(obj.key ?: "")?.owned == true, user)
is Mount -> (holder as? MountViewHolder)?.bind(obj, ownedMounts?.get(obj.key ?: "")?.owned == true, currentMount)
}
}
@ -49,9 +53,4 @@ class MountDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Ada
this.ownedMounts = ownedMounts
notifyDataSetChanged()
}
fun setUser(user: User) {
this.user = user
notifyDataSetChanged()
}
}

View file

@ -17,7 +17,11 @@ class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapt
private var ownedPets: Map<String, OwnedPet>? = null
private var ownedMounts: Map<String, OwnedMount>? = null
private var ownedItems: Map<String, OwnedItem>? = null
private var user: User? = null
var currentPet: String? = null
set(value) {
field = value
notifyDataSetChanged()
}
private val equipEvents = PublishSubject.create<String>()
private val feedEvents = PublishSubject.create<Pair<Pet, Food?>>()
private var ownsSaddles: Boolean = false
@ -29,11 +33,6 @@ class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapt
this.notifyDataSetChanged()
}
fun setUser(user: User) {
this.user = user
notifyDataSetChanged()
}
fun getEquipFlowable(): Flowable<String> {
return equipEvents.toFlowable(BackpressureStrategy.DROP)
}
@ -81,7 +80,7 @@ class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapt
ownedItems?.get(obj.animal + "-eggs") != null,
ownedItems?.get(obj.color + "-hatchingPotions") != null,
ownedMounts?.containsKey(obj.key) == true,
user
currentPet
)
}
}

View file

@ -31,7 +31,16 @@ class StableRecyclerAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var animalIngredientsRetriever: ((Animal, ((Pair<Egg?, HatchingPotion?>) -> Unit)) -> Unit)? = null
private val feedEvents = PublishSubject.create<Pair<Pet, Food?>>()
var itemType: String? = null
private var user: User? = null
var currentPet: String? = null
set(value) {
field = value
notifyDataSetChanged()
}
var currentMount: String? = null
set(value) {
field = value
notifyDataSetChanged()
}
private val equipEvents = PublishSubject.create<String>()
private var existingMounts: List<Mount>? = null
private var ownedMounts: Map<String, OwnedMount>? = null
@ -43,11 +52,6 @@ class StableRecyclerAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
return equipEvents.toFlowable(BackpressureStrategy.DROP)
}
fun setUser(user: User) {
this.user = user
notifyDataSetChanged()
}
private fun canRaiseToMount(pet: Pet): Boolean {
if (pet.type == "special") return false
for (mount in existingMounts ?: emptyList()) {
@ -122,10 +126,10 @@ class StableRecyclerAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
hasUnlockedEgg = ownedItems?.get(item.animal + "-eggs") != null,
hasUnlockedPotion = ownedItems?.get(item.color + "-hatchingPotions") != null,
hasMount = ownedMounts?.containsKey(item.key) == true,
user = user
currentPet = currentPet
)
} else if (item is Mount) {
(holder as? MountViewHolder)?.bind(item, item.numberOwned > 0, user)
(holder as? MountViewHolder)?.bind(item, item.numberOwned > 0, currentMount)
}
return
}

View file

@ -14,6 +14,7 @@ import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBind
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.ui.adapter.AchievementsAdapter
import com.habitrpg.android.habitica.ui.helpers.ToolbarColorHelper
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import io.reactivex.rxjava3.kotlin.Flowables
import io.reactivex.rxjava3.kotlin.combineLatest
import javax.inject.Inject
@ -22,6 +23,8 @@ class AchievementsFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding
@Inject
lateinit var inventoryRepository: InventoryRepository
@Inject
lateinit var userViewModel: MainUserViewModel
override var binding: FragmentRefreshRecyclerviewBinding? = null
@ -125,6 +128,7 @@ class AchievementsFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding
}
)
val user = userViewModel.user.value
val challengeAchievementCount = user?.challengeAchievements?.size ?: 0
if (challengeAchievementCount > 0) {
entries.add(Pair("Challenges won", challengeAchievementCount))

View file

@ -39,23 +39,12 @@ abstract class BaseMainFragment<VB : ViewBinding> : BaseFragment<VB>() {
var usesTabLayout: Boolean = false
var hidesToolbar: Boolean = false
var usesBottomNavigation = false
open var user: User? = null
override fun onAttach(context: Context) {
super.onAttach(context)
if (activity is MainActivity) {
user = activity?.viewModel?.user?.value
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
compositeSubscription.add(userRepository.getUser().subscribe({ user = it }, RxErrorHandler.handleEmptyError()))
if (this.usesBottomNavigation) {
bottomNavigation?.visibility = View.VISIBLE
} else {

View file

@ -28,7 +28,6 @@ import com.habitrpg.android.habitica.extensions.*
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.WorldState
import com.habitrpg.android.habitica.models.WorldStateEvent
import com.habitrpg.android.habitica.models.inventory.Item
import com.habitrpg.android.habitica.models.inventory.Quest
@ -41,7 +40,6 @@ import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.activities.NotificationsActivity
import com.habitrpg.android.habitica.ui.adapter.NavigationDrawerAdapter
import com.habitrpg.android.habitica.ui.fragments.social.TavernDetailFragment
import com.habitrpg.android.habitica.ui.menu.HabiticaDrawerItem
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
@ -339,10 +337,10 @@ class NavigationDrawerFragment : DialogFragment() {
tavernItem?.subtitle = null
}
val specialItems = user.items?.special
val userItems = user.items
var hasSpecialItems = false
if (specialItems != null) {
hasSpecialItems = specialItems.hasSpecialItems
if (userItems != null) {
hasSpecialItems = userItems.hasTransformationItems
}
val item = getItemWithIdentifier(SIDEBAR_SKILLS)
if (item != null) {

View file

@ -18,6 +18,7 @@ import com.habitrpg.android.habitica.ui.adapter.CustomizationRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.MarginDecoration
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import javax.inject.Inject
class AvatarCustomizationFragment :
@ -34,6 +35,8 @@ class AvatarCustomizationFragment :
lateinit var customizationRepository: CustomizationRepository
@Inject
lateinit var inventoryRepository: InventoryRepository
@Inject
lateinit var userViewModel: MainUserViewModel
var type: String? = null
var category: String? = null
@ -52,7 +55,7 @@ class AvatarCustomizationFragment :
adapter.getSelectCustomizationEvents()
.flatMap { customization ->
if (customization.type == "background") {
userRepository.unlockPath(user, customization)
userRepository.unlockPath(userViewModel.user.value, customization)
.flatMap { userRepository.retrieveUser(false, true, true) }
} else {
userRepository.useCustomization(customization.type ?: "", customization.category, customization.identifier ?: "")
@ -63,7 +66,7 @@ class AvatarCustomizationFragment :
compositeSubscription.add(
adapter.getUnlockCustomizationEvents()
.flatMap { customization ->
userRepository.unlockPath(user, customization)
userRepository.unlockPath(userViewModel.user.value, customization)
}
.flatMap { userRepository.retrieveUser(withTasks = false, forced = true) }
.flatMap { inventoryRepository.retrieveInAppRewards() }
@ -112,11 +115,7 @@ class AvatarCustomizationFragment :
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
this.loadCustomizations()
compositeSubscription.add(
userRepository.getUser().subscribeWithErrorHandler {
updateUser(it)
}
)
userViewModel.user.observe(viewLifecycleOwner) { updateUser(it) }
}
override fun onDestroy() {
@ -153,7 +152,8 @@ class AvatarCustomizationFragment :
layoutManager.spanCount = spanCount
}
fun updateUser(user: User) {
fun updateUser(user: User?) {
if (user == null) return
this.updateActiveCustomization(user)
if (adapter.customizationList.size != 0) {
val ownedCustomizations = ArrayList<String>()
@ -162,8 +162,8 @@ class AvatarCustomizationFragment :
} else {
this.loadCustomizations()
}
this.adapter.userSize = this.user?.preferences?.size
this.adapter.hairColor = this.user?.preferences?.hair?.color
this.adapter.userSize = user.preferences?.size
this.adapter.hairColor = user.preferences?.hair?.color
this.adapter.gemBalance = user.gemCount
adapter.notifyDataSetChanged()
}

View file

@ -18,6 +18,7 @@ import com.habitrpg.android.habitica.ui.adapter.CustomizationEquipmentRecyclerVi
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.MarginDecoration
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import io.reactivex.rxjava3.core.Flowable
import javax.inject.Inject
@ -27,6 +28,8 @@ class AvatarEquipmentFragment :
@Inject
lateinit var inventoryRepository: InventoryRepository
@Inject
lateinit var userViewModel: MainUserViewModel
override var binding: FragmentRefreshRecyclerviewBinding? = null
@ -51,7 +54,7 @@ class AvatarEquipmentFragment :
adapter.getSelectCustomizationEvents()
.flatMap { equipment ->
val key = (if (equipment.key?.isNotBlank() != true) activeEquipment else equipment.key) ?: ""
inventoryRepository.equip(user, if (user?.preferences?.costume == true) "costume" else "equipped", key)
inventoryRepository.equip(userViewModel.user.value, if (userViewModel.user.value?.preferences?.costume == true) "costume" else "equipped", key)
}
.subscribe({ }, RxErrorHandler.handleEmptyError())
)
@ -143,7 +146,7 @@ class AvatarEquipmentFragment :
if (this.type == null || user.preferences == null) {
return
}
val outfit = if (user.preferences?.costume == true) this.user?.items?.gear?.costume else this.user?.items?.gear?.equipped
val outfit = if (user.preferences?.costume == true) user.items?.gear?.costume else user.items?.gear?.equipped
val activeEquipment = when (this.type) {
"headAccessory" -> outfit?.headAccessory
"back" -> outfit?.back

View file

@ -44,7 +44,7 @@ class EquipmentDetailFragment :
savedInstanceState: Bundle?
): View? {
compositeSubscription.add(
this.adapter.equipEvents.flatMapMaybe { key -> inventoryRepository.equipGear(user, key, isCostume ?: false).firstElement() }
this.adapter.equipEvents.flatMapMaybe { key -> inventoryRepository.equipGear(null, key, isCostume ?: false).firstElement() }
.subscribe({ }, RxErrorHandler.handleEmptyError())
)
return super.onCreateView(inflater, container, savedInstanceState)

View file

@ -45,10 +45,10 @@ class EquipmentOverviewFragment : BaseMainFragment<FragmentEquipmentOverviewBind
it?.items?.gear?.let {
updateGearData(it)
}
binding?.autoEquipSwitch?.isChecked = user?.preferences?.autoEquip ?: false
binding?.costumeSwitch?.isChecked = user?.preferences?.costume ?: false
binding?.autoEquipSwitch?.isChecked = viewModel.usesAutoEquip
binding?.costumeSwitch?.isChecked = viewModel.usesCostume
binding?.costumeView?.isEnabled = user?.preferences?.costume == true
binding?.costumeView?.isEnabled = viewModel.usesCostume
}
}

View file

@ -1,10 +1,13 @@
package com.habitrpg.android.habitica.ui.fragments.inventory.items
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.activity.result.contract.ActivityResultContracts
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
@ -18,17 +21,23 @@ import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.interactors.HatchPetUseCase
import com.habitrpg.android.habitica.models.inventory.*
import com.habitrpg.android.habitica.models.responses.SkillResponse
import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.models.user.OwnedPet
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.BaseActivity
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.activities.SkillMemberActivity
import com.habitrpg.android.habitica.ui.adapter.inventory.ItemRecyclerAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.helpers.loadImage
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.dialogs.OpenedMysteryitemDialog
import io.reactivex.rxjava3.core.Flowable
import javax.inject.Inject
class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshLayout.OnRefreshListener {
@ -41,11 +50,15 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
lateinit var userRepository: UserRepository
@Inject
internal lateinit var hatchPetUseCase: HatchPetUseCase
@Inject
lateinit var userViewModel: MainUserViewModel
var adapter: ItemRecyclerAdapter? = null
var itemType: String? = null
var transformationItems: MutableList<OwnedItem> = mutableListOf()
var itemTypeText: String? = null
var user: User? = null
private var selectedSpecialItem: SpecialItem? = null
internal var layoutManager: androidx.recyclerview.widget.LinearLayoutManager? = null
override var binding: FragmentItemsBinding? = null
@ -91,7 +104,7 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
adapter = ItemRecyclerAdapter(context, user)
}
binding?.recyclerView?.adapter = adapter
adapter?.useSpecialEvents?.subscribeWithErrorHandler { onSpecialItemSelected(it) }?.let { compositeSubscription.add(it) }
adapter?.let { adapter ->
compositeSubscription.add(
adapter.getSellItemFlowable()
@ -112,7 +125,7 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
)
compositeSubscription.add(
adapter.getOpenMysteryItemFlowable()
.flatMap { inventoryRepository.openMysteryItem(user) }
.flatMap { inventoryRepository.openMysteryItem(userViewModel.user.value) }
.doOnNext {
val activity = activity as? MainActivity
if (activity != null) {
@ -123,7 +136,7 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
dialog.binding.titleView.text = it.text
dialog.binding.descriptionView.text = it.notes
dialog.addButton(R.string.equip, true) { _, _ ->
inventoryRepository.equip(user, "equipped", it.key ?: "").subscribe({}, RxErrorHandler.handleEmptyError())
inventoryRepository.equip(userViewModel.user.value, "equipped", it.key ?: "").subscribe({}, RxErrorHandler.handleEmptyError())
}
dialog.addCloseButton()
dialog.enqueue()
@ -253,7 +266,9 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
adapter?.data = items
}
.map { items -> items.mapNotNull { it.key } }
.flatMap { inventoryRepository.getItems(itemClass, it.toTypedArray()) }
.flatMap {
inventoryRepository.getItems(itemClass, it.toTypedArray())
}
.map {
val itemMap = mutableMapOf<String, Item>()
for (item in it) {
@ -268,6 +283,7 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
RxErrorHandler.handleEmptyError()
)
)
}
compositeSubscription.add(inventoryRepository.getPets().subscribe({ adapter?.setExistingPets(it) }, RxErrorHandler.handleEmptyError()))
@ -286,6 +302,51 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
MainNavigationController.navigate(R.id.marketFragment)
}
private fun onSpecialItemSelected(specialItem: SpecialItem) {
selectedSpecialItem = specialItem
val intent = Intent(activity, SkillMemberActivity::class.java)
memberSelectionResult.launch(intent)
}
private val memberSelectionResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
useSpecialItem(selectedSpecialItem, it.data?.getStringExtra("member_id"))
}
}
private fun useSpecialItem(specialItem: SpecialItem?, memberID: String? = null) {
if (specialItem == null || memberID == null) {
return
}
val observable: Flowable<SkillResponse> =
userRepository.useSkill(specialItem.key, specialItem.target, memberID)
compositeSubscription.add(
observable.subscribe(
{ skillResponse -> this.displaySpecialItemResult(specialItem) },
RxErrorHandler.handleEmptyError()
)
)
}
private fun displaySpecialItemResult(specialItem: SpecialItem?) {
if (!isAdded) return
val activity = activity as? MainActivity
activity?.let {
HabiticaSnackbar.showSnackbar(
it.snackbarContainer,
context?.getString(R.string.used_skill_without_mana, specialItem?.text),
HabiticaSnackbar.SnackbarDisplayType.BLUE
)
}
loadItems()
}
companion object {
private const val ITEM_TYPE_KEY = "CLASS_TYPE_KEY"

View file

@ -68,7 +68,6 @@ class ItemsFragment : BaseMainFragment<FragmentViewpagerBinding>() {
4 -> "special"
else -> ""
}
fragment.user = this@ItemsFragment.user
fragment.itemTypeText =
if (position == 4 && isAdded) getString(R.string.special_items)
else getPageTitle(position)

View file

@ -24,6 +24,7 @@ import com.habitrpg.android.habitica.ui.adapter.inventory.ShopRecyclerAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.RecyclerViewState
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.CurrencyViews
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import javax.inject.Inject
@ -44,6 +45,8 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
lateinit var socialRepository: SocialRepository
@Inject
lateinit var configManager: AppConfigManager
@Inject
lateinit var userViewModel: MainUserViewModel
private var layoutManager: GridLayoutManager? = null
@ -106,7 +109,7 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
this.shopIdentifier = savedInstanceState.getString(SHOP_IDENTIFIER_KEY, "")
}
adapter?.selectedGearCategory = user?.stats?.habitClass ?: ""
adapter?.selectedGearCategory = userViewModel.user.value?.stats?.habitClass ?: ""
if (shop != null) {
adapter?.setShop(shop)
@ -122,15 +125,10 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
}
}
compositeSubscription.add(
userRepository.getUser().subscribe(
{
adapter?.user = user
updateCurrencyView(it)
},
RxErrorHandler.handleEmptyError()
)
)
userViewModel.user.observe(viewLifecycleOwner) {
adapter?.user = it
updateCurrencyView(it)
}
compositeSubscription.add(
socialRepository.getGroup(Group.TAVERN_ID)
@ -183,7 +181,7 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
this.inventoryRepository.retrieveShopInventory(shopUrl)
.map { shop1 ->
if (shop1.identifier == Shop.MARKET) {
val user = user
val user = userViewModel.user.value
val specialCategory = ShopCategory()
specialCategory.text = getString(R.string.special)
if (user?.isValid == true && user.purchased?.plan?.isActive == true) {
@ -315,9 +313,9 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
private const val SHOP_IDENTIFIER_KEY = "SHOP_IDENTIFIER_KEY"
}
private fun updateCurrencyView(user: User) {
currencyView.gold = user.stats?.gp ?: 0.0
currencyView.gems = user.gemCount.toDouble()
currencyView.hourglasses = user.hourglassCount.toDouble()
private fun updateCurrencyView(user: User?) {
currencyView.gold = user?.stats?.gp ?: 0.0
currencyView.gems = user?.gemCount?.toDouble() ?: 0.0
currencyView.hourglasses = user?.hourglassCount?.toDouble() ?: 0.0
}
}

View file

@ -18,6 +18,7 @@ import com.habitrpg.android.habitica.ui.adapter.inventory.MountDetailRecyclerAda
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.MarginDecoration
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import javax.inject.Inject
class MountDetailRecyclerFragment :
@ -26,6 +27,8 @@ class MountDetailRecyclerFragment :
@Inject
internal lateinit var inventoryRepository: InventoryRepository
@Inject
lateinit var userViewModel: MainUserViewModel
var adapter: MountDetailRecyclerAdapter? = null
var animalType: String? = null
@ -86,14 +89,15 @@ class MountDetailRecyclerFragment :
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
this.loadItems()
adapter?.getEquipFlowable()?.flatMap { key -> inventoryRepository.equip(user, "mount", key) }
adapter?.getEquipFlowable()?.flatMap { key -> inventoryRepository.equip(null, "mount", key) }
?.subscribe(
{
user?.let { updatedUser -> adapter?.setUser(updatedUser) }
adapter?.currentMount = it.currentMount
},
RxErrorHandler.handleEmptyError()
)?.let { compositeSubscription.add(it) }
}
userViewModel.user.observe(viewLifecycleOwner) { adapter?.currentMount = it?.currentMount }
if (savedInstanceState != null) {
this.animalType = savedInstanceState.getString(ANIMAL_TYPE_KEY, "")
@ -134,7 +138,6 @@ class MountDetailRecyclerFragment :
return@map mountMap
}.doOnNext {
adapter?.setOwnedMounts(it)
user?.let { updatedUser -> adapter?.setUser(updatedUser) }
},
{ unsortedAnimals, ownedAnimals ->
val items = mutableListOf<Any>()

View file

@ -22,6 +22,7 @@ import com.habitrpg.android.habitica.ui.adapter.inventory.PetDetailRecyclerAdapt
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.fragments.inventory.items.ItemDialogFragment
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.kotlin.Flowables
import javax.inject.Inject
@ -34,6 +35,8 @@ class PetDetailRecyclerFragment :
lateinit var inventoryRepository: InventoryRepository
@Inject
lateinit var feedPetUseCase: FeedPetUseCase
@Inject
lateinit var userViewModel: MainUserViewModel
var adapter: PetDetailRecyclerAdapter = PetDetailRecyclerAdapter()
private var animalType: String? = null
@ -109,14 +112,15 @@ class PetDetailRecyclerFragment :
compositeSubscription.add(
adapter.getEquipFlowable()
.flatMap { key -> inventoryRepository.equip(user, "pet", key) }
.flatMap { key -> inventoryRepository.equip(null, "pet", key) }
.subscribe(
{
user?.let { updatedUser -> adapter.setUser(updatedUser) }
adapter.currentPet = it.currentPet
},
RxErrorHandler.handleEmptyError()
)
)
userViewModel.user.observe(viewLifecycleOwner) { adapter.currentPet = it?.currentPet }
compositeSubscription.add(adapter.feedFlowable.subscribe({ showFeedingDialog(it.first, it.second) }, RxErrorHandler.handleEmptyError()))
view.post { setGridSpanCount(view.width) }
@ -168,7 +172,6 @@ class PetDetailRecyclerFragment :
return@map petMap
}.doOnNext {
adapter.setOwnedPets(it)
user?.let { updatedUser -> adapter.setUser(updatedUser) }
}
).map {
val items = mutableListOf<Any>()

View file

@ -56,8 +56,7 @@ class StableFragment : BaseMainFragment<FragmentViewpagerBinding>() {
fragment.itemType = "mounts"
}
}
fragment.user = this@StableFragment.user
fragment.itemTypeText = getPageTitle(position).toString()
fragment.itemTypeText = getPageTitle(position)
return fragment
}

View file

@ -23,6 +23,7 @@ import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.MarginDecoration
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.kotlin.combineLatest
@ -39,11 +40,12 @@ class StableRecyclerFragment :
lateinit var userRepository: UserRepository
@Inject
lateinit var configManager: AppConfigManager
@Inject
lateinit var userViewModel: MainUserViewModel
var adapter: StableRecyclerAdapter? = null
var itemType: String? = null
var itemTypeText: String? = null
var user: User? = null
internal var layoutManager: androidx.recyclerview.widget.GridLayoutManager? = null
override var binding: FragmentRefreshRecyclerviewBinding? = null
@ -96,7 +98,6 @@ class StableRecyclerFragment :
adapter = binding?.recyclerView?.adapter as? StableRecyclerAdapter
if (adapter == null) {
adapter = StableRecyclerAdapter()
user?.let { adapter?.setUser(it) }
adapter?.animalIngredientsRetriever = { animal, callback ->
Maybe.zip(
inventoryRepository.getItems(Egg::class.java, arrayOf(animal.animal)).firstElement(),
@ -119,11 +120,15 @@ class StableRecyclerFragment :
adapter?.let {
compositeSubscription.add(
it.getEquipFlowable()
.flatMap { key -> inventoryRepository.equip(user, if (itemType == "pets") "pet" else "mount", key) }
.flatMap { key -> inventoryRepository.equip(null, if (itemType == "pets") "pet" else "mount", key) }
.subscribe({ }, RxErrorHandler.handleEmptyError())
)
}
}
userViewModel.user.observe(viewLifecycleOwner) {
adapter?.currentPet = it?.currentPet
adapter?.currentMount = it?.currentMount
}
this.loadItems()
view.post { setGridSpanCount(view.width) }

View file

@ -64,11 +64,11 @@ class AccountPreferenceFragment: BasePreferencesFragment(),
override fun onResume() {
super.onResume()
preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
}
override fun onPause() {
preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
super.onPause()
}
@ -130,8 +130,8 @@ class AccountPreferenceFragment: BasePreferencesFragment(),
preference?.summary = value
}
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
when(preference?.key) {
override fun onPreferenceTreeClick(preference: Preference): Boolean {
when(preference.key) {
"username" -> showLoginNameDialog()
"confirm_username" -> showConfirmUsernameDialog()
"email" -> {

View file

@ -33,7 +33,7 @@ class DayStartPreferenceDialogFragment : PreferenceDialogFragmentCompat() {
return lastHour.toString() + ":00"
}
override fun onCreateDialogView(context: Context?): View {
override fun onCreateDialogView(context: Context): View {
val wrapper = LinearLayout(context)
wrapper.orientation = LinearLayout.VERTICAL
picker = TimePicker(context)

View file

@ -19,12 +19,12 @@ class EmailNotificationsPreferencesFragment : BasePreferencesFragment(), SharedP
override fun onResume() {
super.onResume()
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
}
override fun onPause() {
super.onPause()
preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
preferenceScreen.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
}
override fun setupPreferences() { /* no-on */ }

View file

@ -62,16 +62,16 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
override fun setupPreferences() {
timePreference = findPreference("reminder_time") as? TimePreference
val useReminder = preferenceManager.sharedPreferences.getBoolean("use_reminder", false)
timePreference?.isEnabled = useReminder
val useReminder = preferenceManager.sharedPreferences?.getBoolean("use_reminder", false)
timePreference?.isEnabled = useReminder ?: false
pushNotificationsPreference = findPreference("pushNotifications") as? PreferenceScreen
val usePushNotifications = preferenceManager.sharedPreferences.getBoolean("usePushNotifications", true)
pushNotificationsPreference?.isEnabled = usePushNotifications
val usePushNotifications = preferenceManager.sharedPreferences?.getBoolean("usePushNotifications", true)
pushNotificationsPreference?.isEnabled = usePushNotifications ?: false
emailNotificationsPreference = findPreference("emailNotifications") as? PreferenceScreen
val useEmailNotifications = preferenceManager.sharedPreferences.getBoolean("useEmailNotifications", true)
emailNotificationsPreference?.isEnabled = useEmailNotifications
val useEmailNotifications = preferenceManager.sharedPreferences?.getBoolean("useEmailNotifications", true)
emailNotificationsPreference?.isEnabled = useEmailNotifications ?: false
classSelectionPreference = findPreference("choose_class")
@ -80,7 +80,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
serverUrlPreference = findPreference("server_url") as? ListPreference
serverUrlPreference?.isVisible = false
serverUrlPreference?.summary = preferenceManager.sharedPreferences.getString("server_url", "")
serverUrlPreference?.summary = preferenceManager.sharedPreferences?.getString("server_url", "")
val themePreference = findPreference("theme_name") as? ListPreference
themePreference?.summary = themePreference?.entry ?: "Default"
@ -101,11 +101,11 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
override fun onResume() {
super.onResume()
preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
}
override fun onPause() {
preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
super.onPause()
}

View file

@ -19,12 +19,12 @@ class PushNotificationsPreferencesFragment : BasePreferencesFragment(), SharedPr
override fun onResume() {
super.onResume()
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
}
override fun onPause() {
super.onPause()
preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
preferenceScreen.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
}
override fun setupPreferences() { /* no-on */ }

View file

@ -34,7 +34,7 @@ class TimePreferenceDialogFragment : PreferenceDialogFragmentCompat() {
return lastHour.toString() + ":" + String.format(Locale.UK, "%02d", lastMinute)
}
override fun onCreateDialogView(context: Context?): View {
override fun onCreateDialogView(context: Context): View {
picker = TimePicker(context)
picker.setIs24HourView(android.text.format.DateFormat.is24HourFormat(context))
return picker

View file

@ -23,12 +23,14 @@ import com.habitrpg.android.habitica.ui.activities.SkillTasksActivity
import com.habitrpg.android.habitica.ui.adapter.SkillsRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.Companion.showSnackbar
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import javax.inject.Inject
class SkillsFragment : BaseMainFragment<FragmentSkillsBinding>() {
internal var adapter: SkillsRecyclerViewAdapter? = null
@ -36,20 +38,16 @@ class SkillsFragment : BaseMainFragment<FragmentSkillsBinding>() {
override var binding: FragmentSkillsBinding? = null
@Inject
lateinit var userViewModel: MainUserViewModel
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSkillsBinding {
return FragmentSkillsBinding.inflate(inflater, container, false)
}
override var user: User? = null
set(value) {
field = value
checkUserLoadSkills()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
adapter = SkillsRecyclerViewAdapter()
adapter?.useSkillEvents?.subscribeWithErrorHandler { onSkillSelected(it) }?.let { compositeSubscription.add(it) }
checkUserLoadSkills()
this.tutorialStepIdentifier = "skills"
this.tutorialText = getString(R.string.tutorial_skills)
@ -62,20 +60,21 @@ class SkillsFragment : BaseMainFragment<FragmentSkillsBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
userViewModel.user.observe(viewLifecycleOwner) { user ->
user?.let { checkUserLoadSkills(it) }
}
binding?.recyclerView?.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(activity)
binding?.recyclerView?.adapter = adapter
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
}
private fun checkUserLoadSkills() {
if (user == null || adapter == null) {
private fun checkUserLoadSkills(user: User) {
if (adapter == null) {
return
}
adapter?.mana = this.user?.stats?.mp ?: 0.0
adapter?.level = this.user?.stats?.lvl ?: 0
adapter?.specialItems = this.user?.items?.special
user?.let { user ->
adapter?.mana = user.stats?.mp ?: 0.0
adapter?.level = user.stats?.lvl ?: 0
adapter?.specialItems = user.items?.special
Flowable.combineLatest(
userRepository.getSkills(user),
userRepository.getSpecialItems(user),
@ -89,9 +88,7 @@ class SkillsFragment : BaseMainFragment<FragmentSkillsBinding>() {
}
return@combineLatest allEntries
}
)
.subscribe({ skills -> adapter?.setSkillList(skills) }, RxErrorHandler.handleEmptyError())
}
).subscribe({ skills -> adapter?.setSkillList(skills) }, RxErrorHandler.handleEmptyError())
}
private fun onSkillSelected(skill: Skill) {

View file

@ -7,6 +7,7 @@ import android.os.Bundle
import android.view.*
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.MainNavDirections
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
@ -88,7 +89,14 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
{ member ->
setReceivingUser(member.username, member.id)
activity?.title = member.displayName
chatAdapter = InboxAdapter(user, member)
chatAdapter = InboxAdapter(viewModel.user.value, member)
chatAdapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if (positionStart == 0) {
binding?.recyclerView?.scrollToPosition(0)
}
}
})
viewModel.messages.observe(this.viewLifecycleOwner) {
markMessagesAsRead(it)
chatAdapter?.submitList(it)
@ -170,7 +178,7 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
}
private fun markMessagesAsRead(messages: List<ChatMessage>) {
socialRepository.markSomePrivateMessagesAsRead(user, messages)
socialRepository.markSomePrivateMessagesAsRead(viewModel.user.value, messages)
}
private fun startAutoRefreshing() {
@ -215,7 +223,6 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
binding?.recyclerView?.scrollToPosition(0)
viewModel.invalidateDataSource()
},
{ error ->

View file

@ -41,7 +41,7 @@ class InboxOverviewFragment : BaseMainFragment<FragmentInboxBinding>(), androidx
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
this.hidesToolbar = true
compositeSubscription.add(this.socialRepository.markPrivateMessagesRead(user).subscribe({ }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(this.socialRepository.markPrivateMessagesRead(null).subscribe({ }, RxErrorHandler.handleEmptyError()))
return super.onCreateView(inflater, container, savedInstanceState)
}

View file

@ -26,6 +26,7 @@ import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils
import com.habitrpg.android.habitica.ui.helpers.MarkdownParser
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import javax.inject.Inject
import javax.inject.Named
@ -38,6 +39,8 @@ class QuestDetailFragment : BaseMainFragment<FragmentQuestDetailBinding>() {
lateinit var inventoryRepository: InventoryRepository
@field:[Inject Named(AppModule.NAMED_USER_ID)]
lateinit var userId: String
@Inject
lateinit var userViewModel: MainUserViewModel
override var binding: FragmentQuestDetailBinding? = null
@ -101,7 +104,7 @@ class QuestDetailFragment : BaseMainFragment<FragmentQuestDetailBinding>() {
compositeSubscription.add(
socialRepository.getMember(quest?.leader).subscribe(
{ member ->
if (context != null && binding?.questLeaderView != null && member != null) {
if (context != null && binding?.questLeaderView != null) {
binding?.questLeaderView?.text = context?.getString(R.string.quest_leader_header, member.displayName)
}
},
@ -109,6 +112,7 @@ class QuestDetailFragment : BaseMainFragment<FragmentQuestDetailBinding>() {
)
)
val user = userViewModel.user.value
if (binding?.questResponseWrapper != null) {
if (userId != party?.quest?.leader && user?.party?.quest?.key == group.quest?.key && user?.party?.quest?.RSVPNeeded == false) {
binding?.questLeaveButton?.visibility = View.VISIBLE

View file

@ -29,6 +29,7 @@ import com.habitrpg.android.habitica.ui.viewHolders.tasks.DailyViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.HabitViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.RewardViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.TodoViewHolder
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import retrofit2.HttpException
@ -41,6 +42,9 @@ class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>
lateinit var challengeRepository: ChallengeRepository
@Inject
lateinit var socialRepository: SocialRepository
@Inject
lateinit var userViewModel: MainUserViewModel
override var binding: FragmentChallengeDetailBinding? = null
@ -236,7 +240,7 @@ class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>
binding?.creatorAvatarview?.setAvatar(creator)
binding?.creatorLabel?.tier = creator.contributor?.level ?: 0
binding?.creatorLabel?.username = creator.displayName
isCreator = creator.id == user?.id
isCreator = creator.id == userViewModel.userID
this.activity?.invalidateOptionsMenu()
}

View file

@ -30,6 +30,7 @@ import com.habitrpg.android.habitica.ui.activities.GroupFormActivity
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils
import com.habitrpg.android.habitica.ui.helpers.setMarkdown
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import javax.inject.Inject
import kotlin.math.roundToInt
@ -40,6 +41,8 @@ class NoPartyFragmentFragment : BaseMainFragment<FragmentNoPartyBinding>() {
lateinit var socialRepository: SocialRepository
@Inject
lateinit var configManager: AppConfigManager
@Inject
lateinit var userViewModel: MainUserViewModel
override var binding: FragmentNoPartyBinding? = null
@ -65,7 +68,7 @@ class NoPartyFragmentFragment : BaseMainFragment<FragmentNoPartyBinding>() {
parentFragmentManager.popBackStack()
MainNavigationController.navigate(
R.id.partyFragment,
bundleOf(Pair("partyID", user?.party?.id))
bundleOf(Pair("partyID", userViewModel.partyID))
)
},
RxErrorHandler.handleEmptyError()
@ -92,7 +95,7 @@ class NoPartyFragmentFragment : BaseMainFragment<FragmentNoPartyBinding>() {
binding?.usernameTextview?.setOnClickListener {
val clipboard = context?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
val clip = ClipData.newPlainText(context?.getString(R.string.username), user?.username)
val clip = ClipData.newPlainText(context?.getString(R.string.username), userViewModel.username)
clipboard?.setPrimaryClip(clip)
val activity = activity
if (activity != null) {
@ -103,7 +106,7 @@ class NoPartyFragmentFragment : BaseMainFragment<FragmentNoPartyBinding>() {
binding?.createPartyButton?.setOnClickListener {
val bundle = Bundle()
bundle.putString("groupType", "party")
bundle.putString("leader", user?.id)
bundle.putString("leader", userViewModel.userID)
val intent = Intent(activity, GroupFormActivity::class.java)
intent.putExtras(bundle)
intent.flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
@ -132,14 +135,15 @@ class NoPartyFragmentFragment : BaseMainFragment<FragmentNoPartyBinding>() {
}
}
if ((user?.invitations?.parties?.count() ?: 0) > 0) {
val partyInvitations = userViewModel.partyInvitations
if (partyInvitations.size > 0) {
binding?.invitationWrapper?.visibility = View.VISIBLE
user?.invitations?.parties?.let { binding?.invitationsView?.setInvitations(it) }
binding?.invitationsView?.setInvitations(partyInvitations)
} else {
binding?.invitationWrapper?.visibility = View.GONE
}
binding?.usernameTextview?.text = user?.formattedUsername
binding?.usernameTextview?.text = userViewModel.formattedUsername
}
private val groupFormResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
@ -164,7 +168,7 @@ class NoPartyFragmentFragment : BaseMainFragment<FragmentNoPartyBinding>() {
}
MainNavigationController.navigate(
R.id.partyFragment,
bundleOf(Pair("partyID", user?.party?.id))
bundleOf(Pair("partyID", userViewModel.partyID))
)
},
RxErrorHandler.handleEmptyError()
@ -196,12 +200,11 @@ class NoPartyFragmentFragment : BaseMainFragment<FragmentNoPartyBinding>() {
companion object {
fun newInstance(user: User?): NoPartyFragmentFragment {
fun newInstance(): NoPartyFragmentFragment {
val args = Bundle()
val fragment = NoPartyFragmentFragment()
fragment.arguments = args
fragment.user = user
return fragment
}
}

View file

@ -96,15 +96,11 @@ class PartyFragment : BaseMainFragment<FragmentViewpagerBinding>() {
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
val group = viewModel.getGroupData().value
if (group != null && this.user != null) {
if (group.leaderID == this.user?.id) {
inflater.inflate(R.menu.menu_party_admin, menu)
if (group.memberCount > 1) {
menu.findItem(R.id.menu_guild_leave).isVisible = false
}
} else {
inflater.inflate(R.menu.menu_party, menu)
}
if (viewModel.isLeader) {
inflater.inflate(R.menu.menu_party_admin, menu)
menu.findItem(R.id.menu_guild_leave).isVisible = group?.memberCount != 1
} else {
inflater.inflate(R.menu.menu_party, menu)
}
}
@ -159,7 +155,7 @@ class PartyFragment : BaseMainFragment<FragmentViewpagerBinding>() {
private val sendInvitesResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
val inviteData = HashMap<String, Any>()
inviteData["inviter"] = user?.profile?.name ?: ""
inviteData["inviter"] = viewModel.user.value?.profile?.name ?: ""
val emails = it.data?.getStringArrayExtra(GroupInviteActivity.EMAILS_KEY)
if (emails != null && emails.isNotEmpty()) {
val invites = ArrayList<HashMap<String, String>>()

View file

@ -20,6 +20,7 @@ import com.habitrpg.android.habitica.helpers.DeviceName
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.schedulers.Schedulers
import javax.inject.Inject
@ -42,6 +43,8 @@ class BugFixFragment : BaseMainFragment<FragmentSupportBugFixBinding>() {
@Inject
lateinit var appConfigManager: AppConfigManager
@Inject
lateinit var userViewModel: MainUserViewModel
override fun injectFragment(component: UserComponent) {
component.inject(this)
@ -124,8 +127,7 @@ class BugFixFragment : BaseMainFragment<FragmentSupportBugFixBinding>() {
}
bodyOfEmail += newLine + Uri.encode("User ID: $userId")
val user = this.user
if (user != null) {
userViewModel.user.value?.let { user ->
bodyOfEmail += newLine + Uri.encode("Level: " + (user.stats?.lvl ?: 0)) +
newLine + Uri.encode(
"Class: " + (if (user.preferences?.disableClasses == true) "Disabled" else (user.stats?.habitClass

View file

@ -19,7 +19,7 @@ class MountViewHolder(parent: ViewGroup, private val equipEvents: PublishSubject
private var binding: MountOverviewItemBinding = MountOverviewItemBinding.bind(itemView)
private var owned: Boolean = false
var animal: Mount? = null
private var user: User? = null
private var currentMount: String? = null
var resources: Resources = itemView.resources
@ -27,10 +27,10 @@ class MountViewHolder(parent: ViewGroup, private val equipEvents: PublishSubject
itemView.setOnClickListener(this)
}
fun bind(item: Mount, owned: Boolean, user: User?) {
fun bind(item: Mount, owned: Boolean, currentMount: String?) {
animal = item
this.owned = owned
this.user = user
this.currentMount = currentMount
binding.titleTextView.visibility = View.GONE
binding.ownedTextView.visibility = View.GONE
val imageName = "stable_Mount_Icon_" + item.animal + "-" + item.color
@ -39,7 +39,7 @@ class MountViewHolder(parent: ViewGroup, private val equipEvents: PublishSubject
binding.imageView.alpha = 0.2f
}
binding.imageView.background = null
binding.activeIndicator.visibility = if (user?.currentMount.equals(animal?.key)) View.VISIBLE else View.GONE
binding.activeIndicator.visibility = if (currentMount.equals(animal?.key)) View.VISIBLE else View.GONE
DataBindingUtils.loadImage(itemView.context, imageName) {
val drawable = if (owned) it else BitmapDrawable(itemView.context.resources, it.toBitmap().extractAlpha())
binding.imageView.background = drawable
@ -53,7 +53,7 @@ class MountViewHolder(parent: ViewGroup, private val equipEvents: PublishSubject
val menu = BottomSheetMenu(itemView.context)
menu.setTitle(animal?.text)
val hasCurrentMount = user?.currentMount.equals(animal?.key)
val hasCurrentMount = currentMount.equals(animal?.key)
val labelId = if (hasCurrentMount) R.string.unequip else R.string.equip
menu.addMenuItem(BottomSheetMenuItem(resources.getString(labelId)))
menu.setSelectionRunnable {

View file

@ -24,7 +24,7 @@ class PetViewHolder(parent: ViewGroup, private val equipEvents: PublishSubject<S
private var potionCount: Int = 0
private var ownsSaddles = false
private var animal: Pet? = null
private var user: User? = null
private var currentPet: String? = null
private var binding: PetDetailItemBinding = PetDetailItemBinding.bind(itemView)
@ -49,7 +49,7 @@ class PetViewHolder(parent: ViewGroup, private val equipEvents: PublishSubject<S
hasUnlockedEgg: Boolean,
hasUnlockedPotion: Boolean,
hasMount: Boolean,
user: User?
currentPet: String?
) {
this.animal = item
isOwned = trained > 0
@ -61,7 +61,7 @@ class PetViewHolder(parent: ViewGroup, private val equipEvents: PublishSubject<S
this.hasUnlockedEgg = hasUnlockedEgg
this.hasUnlockedPotion = hasUnlockedPotion
this.hasMount = hasMount
this.user = user
this.currentPet = currentPet
binding.imageView.visibility = View.VISIBLE
binding.itemWrapper.visibility = View.GONE
binding.checkmarkView.visibility = View.GONE
@ -93,7 +93,7 @@ class PetViewHolder(parent: ViewGroup, private val equipEvents: PublishSubject<S
binding.trainedProgressBar.progressBackgroundTintMode = PorterDuff.Mode.SRC_OVER
}
binding.imageView.background = null
binding.activeIndicator.visibility = if (user?.currentPet.equals(animal?.key)) View.VISIBLE else View.GONE
binding.activeIndicator.visibility = if (currentPet.equals(animal?.key)) View.VISIBLE else View.GONE
DataBindingUtils.loadImage(itemView.context, imageName) {
val resources = itemView.context.resources ?: return@loadImage
val drawable = if (trained == 0) BitmapDrawable(resources, it.toBitmap().extractAlpha()) else it
@ -110,7 +110,7 @@ class PetViewHolder(parent: ViewGroup, private val equipEvents: PublishSubject<S
val menu = BottomSheetMenu(context)
menu.setTitle(animal?.text)
val hasCurrentPet = user?.currentPet.equals(animal?.key)
val hasCurrentPet = currentPet.equals(animal?.key)
val labelId = if (hasCurrentPet) R.string.unequip else R.string.equip
menu.addMenuItem(BottomSheetMenuItem(itemView.resources.getString(labelId)))

View file

@ -15,20 +15,13 @@ import javax.inject.Inject
abstract class BaseViewModel(initializeComponent: Boolean = true) : ViewModel() {
val isUserFainted: Boolean
get() = (user.value?.stats?.hp ?: 1.0) == 0.0
val isUserInParty: Boolean
get() = user.value?.hasParty == true
@Inject
lateinit var userRepository: UserRepository
@Inject
lateinit var userViewModel: MainUserViewModel
private val _user: MutableLiveData<User?> by lazy {
loadUserFromLocal()
MutableLiveData<User?>()
}
val user: LiveData<User?> by lazy {
_user
userViewModel.user
}
init {
@ -47,11 +40,6 @@ abstract class BaseViewModel(initializeComponent: Boolean = true) : ViewModel()
internal val disposable = CompositeDisposable()
internal fun loadUserFromLocal() {
disposable.add(userRepository.getUser().observeOn(AndroidSchedulers.mainThread())
.subscribe({ _user.value = it }, RxErrorHandler.handleEmptyError()))
}
fun updateUser(path: String, value: Any) {
disposable.add(userRepository.updateUser(path, value)
.subscribe({ }, RxErrorHandler.handleEmptyError()))

View file

@ -249,7 +249,8 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali
}
fun likeMessage(message: ChatMessage) {
val index = _chatMessages.value?.indexOf(message) ?: return
val index = _chatMessages.value?.indexOf(message)
if (index == null || index < 0) return
disposable.add(socialRepository.likeMessage(message).subscribe(
{
val list = _chatMessages.value?.toMutableList()

View file

@ -0,0 +1,62 @@
package com.habitrpg.android.habitica.ui.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.invitations.GenericInvitation
import com.habitrpg.android.habitica.models.invitations.PartyInvite
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import javax.inject.Inject
class MainUserViewModel(val userRepository: UserRepository) {
val formattedUsername: CharSequence?
get() = user.value?.formattedUsername
val partyInvitations: List<PartyInvite>
get() = user.value?.invitations?.parties ?: emptyList()
val userID: String?
get() = user.value?.id
val username: CharSequence
get() = user.value?.username ?: ""
val partyID: String?
get() = user.value?.party?.id
val isUserFainted: Boolean
get() = (user.value?.stats?.hp ?: 1.0) == 0.0
val isUserInParty: Boolean
get() = user.value?.hasParty == true
private val _user: MutableLiveData<User?> by lazy {
loadUserFromLocal()
MutableLiveData<User?>()
}
val user: LiveData<User?> by lazy {
_user
}
init {
HabiticaBaseApplication.userComponent?.inject(this)
}
fun onCleared() {
userRepository.close()
disposable.clear()
}
internal val disposable = CompositeDisposable()
internal fun loadUserFromLocal() {
disposable.add(userRepository.getUser().observeOn(AndroidSchedulers.mainThread())
.subscribe({ _user.value = it }, RxErrorHandler.handleEmptyError()))
}
fun updateUser(path: String, value: Any) {
disposable.add(userRepository.updateUser(path, value)
.subscribe({ }, RxErrorHandler.handleEmptyError()))
}
}

View file

@ -9,6 +9,10 @@ import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel
import javax.inject.Inject
class EquipmentOverviewViewModel(savedStateHandle: SavedStateHandle): BaseViewModel() {
val usesAutoEquip: Boolean
get() = user.value?.preferences?.autoEquip == true
val usesCostume: Boolean
get() = user.value?.preferences?.costume == true
@Inject
lateinit var inventoryRepository: InventoryRepository

View file

@ -8,7 +8,7 @@ import androidx.preference.PreferenceViewHolder
import com.habitrpg.android.habitica.R
class ExtraLabelPreference(
context: Context?,
context: Context,
attrs: AttributeSet?
) : Preference(context, attrs) {
var extraText: String? = null
@ -18,9 +18,9 @@ class ExtraLabelPreference(
layoutResource = R.layout.preference_button
}
override fun onBindViewHolder(holder: PreferenceViewHolder?) {
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
val textView = holder?.itemView?.findViewById<TextView>(R.id.extra_label)
val textView = holder.itemView.findViewById<TextView>(R.id.extra_label)
textView?.text = extraText
extraTextColor?.let {
textView?.setTextColor(it)

View file

@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import androidx.core.graphics.drawable.toBitmap
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.DialogPetSuggestHatchBinding
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
@ -36,6 +37,7 @@ class PetSuggestHatchDialog(context: Context) : HabiticaAlertDialog(context) {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as? LayoutInflater
inflater?.let { binding = DialogPetSuggestHatchBinding.inflate(it) }
setAdditionalContentView(binding.root)
HabiticaBaseApplication.userComponent?.inject(this)
}
fun configure(pet: Animal, egg: Egg?, potion: HatchingPotion?, eggCount: Int, potionCount: Int, hasUnlockedEgg: Boolean, hasUnlockedPotion: Boolean, hasMount: Boolean) {

View file

@ -11,6 +11,7 @@ import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toDrawable
import androidx.core.os.bundleOf
import com.google.firebase.analytics.FirebaseAnalytics
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
@ -319,6 +320,7 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
}
private fun buyItem(quantity: Int) {
val application = ownerActivity?.application as? HabiticaBaseApplication
FirebaseAnalytics.getInstance(context).logEvent(
"item_purchased",
bundleOf(
@ -373,7 +375,7 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
"hourglasses" -> ContextCompat.getColor(context, R.color.text_brand)
else -> 0
}
(ownerActivity as? SnackbarActivity)?.showSnackbar(
((application?.currentActivity?.get() ?: getActivity() ?: ownerActivity) as? SnackbarActivity)?.showSnackbar(
content = text,
rightIcon = priceLabel.compoundDrawables[0],
rightTextColor = rightTextColor,

View file

@ -158,7 +158,8 @@ class YesterdailyDialog private constructor(context: Context, private val userRe
private fun configureTaskView(taskView: View, task: Task) {
val completed = !task.isDisplayedActive
val checkmark = taskView.findViewById<View>(R.id.checkmark)
val checkmark = taskView.findViewById<ImageView>(R.id.checkmark)
checkmark?.drawable?.setTint(ContextCompat.getColor(context, R.color.gray_400))
val checkboxHolder = taskView.findViewById<View>(R.id.checkBoxHolder)
val checkboxBackground = taskView.findViewById<View>(R.id.checkbox_background)
checkmark?.visibility = if (completed) View.VISIBLE else View.GONE

View file

@ -11,14 +11,7 @@ import com.habitrpg.android.habitica.models.ContentGear
import com.habitrpg.android.habitica.models.ContentResult
import com.habitrpg.android.habitica.models.FAQArticle
import com.habitrpg.android.habitica.models.Skill
import com.habitrpg.android.habitica.models.inventory.Customization
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.Equipment
import com.habitrpg.android.habitica.models.inventory.Food
import com.habitrpg.android.habitica.models.inventory.HatchingPotion
import com.habitrpg.android.habitica.models.inventory.Mount
import com.habitrpg.android.habitica.models.inventory.Pet
import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.models.inventory.*
import io.realm.RealmList
import java.lang.reflect.Type
@ -101,6 +94,13 @@ class ContentDeserializer : JsonDeserializer<ContentResult> {
result.spells.add(skill)
}
}
if (obj.has("special")) {
for (entry in obj.get("special").asJsonObject.entrySet()) {
result.special.add(context.deserialize(entry.value, SpecialItem::class.java))
}
}
result.appearances = context.deserialize(obj.get("appearances"), object : TypeToken<RealmList<Customization>>() {}.type)
result.backgrounds = context.deserialize(obj.get("backgrounds"), object : TypeToken<RealmList<Customization>>() {}.type)
val noBackground = Customization()

View file

@ -13,10 +13,12 @@ class OwnedItemListDeserializer : JsonDeserializer<List<OwnedItem>> {
val entrySet = json?.asJsonObject?.entrySet()
if (entrySet != null) {
for (entry in entrySet) {
val item = OwnedItem()
item.key = entry.key
item.numberOwned = entry.value.asInt
ownedItems.add(item)
if (entry.value.isJsonPrimitive){
val item = OwnedItem()
item.key = entry.key
item.numberOwned = entry.value.asInt
ownedItems.add(item)
}
}
}
return ownedItems

View file

@ -99,8 +99,7 @@ class UserDeserializer : JsonDeserializer<User> {
item.key = "inventory_present"
item.userID = user.id
item.numberOwned = user.purchased?.plan?.mysteryItemCount ?: 0
user.items?.special?.ownedItems = RealmList()
user.items?.special?.ownedItems?.add(item)
user.items?.special?.add(item)
user.items?.setItemTypes()
}
if (obj.has("auth")) {

View file

@ -61,7 +61,7 @@ class AddTaskWidgetProvider : BaseWidgetProvider() {
}
private fun getSelectedTaskType(widgetId: Int): TaskType {
val preferences = PreferenceManager.getDefaultSharedPreferences(this.context)
return TaskType.from(preferences.getString("add_task_widget_$widgetId", TaskType.HABIT.value)) ?: TaskType.HABIT
val preferences = context?.let { PreferenceManager.getDefaultSharedPreferences(it) }
return TaskType.from(preferences?.getString("add_task_widget_$widgetId", TaskType.HABIT.value)) ?: TaskType.HABIT
}
}

View file

@ -1,37 +0,0 @@
package com.habitrpg.android.habitica.models.user
import io.kotest.common.ExperimentalKotest
import io.kotest.core.spec.style.WordSpec
import io.kotest.datatest.withData
import io.kotest.matchers.shouldBe
@ExperimentalKotest
class SpecialItemsTest : WordSpec({
data class SpecialItemsData(val snowBalls: Int, val seafoam: Int, val shinyseed: Int, val spookySparkles: Int)
lateinit var items: SpecialItems
beforeEach {
items = SpecialItems()
}
"hasSpecialItems" should {
"false if none are owned" {
items.hasSpecialItems shouldBe false
}
"true if any are owned" {
items.snowball = 4
items.hasSpecialItems shouldBe true
}
withData(
SpecialItemsData(1, 0, 0, 0),
SpecialItemsData(0, 1, 0, 0),
SpecialItemsData(0, 0, 1, 0),
SpecialItemsData(0, 0, 0, 1),
SpecialItemsData(1, 3, 4, 8)
) { (snowballs, seafoam, shinyseeds, spookySparkles) ->
items.snowball = snowballs
items.seafoam = seafoam
items.shinySeed = shinyseeds
items.spookySparkles = spookySparkles
items.hasSpecialItems shouldBe true
}
}
})