Implement new faint screen

This commit is contained in:
Phillip Thelen 2022-05-26 11:56:06 +02:00
parent 6c3585040d
commit fd4c49ecde
17 changed files with 570 additions and 166 deletions

View file

@ -71,6 +71,13 @@
android:screenOrientation="unspecified"
tools:ignore="UnusedAttribute">
</activity>
<activity
android:name=".ui.activities.DeathActivity"
android:parentActivityName=".ui.activities.MainActivity"
android:label="@string/faint_header"
android:screenOrientation="unspecified"
tools:ignore="UnusedAttribute">
</activity>
<activity
android:name=".ui.activities.NotificationsActivity"
android:parentActivityName=".ui.activities.MainActivity"

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<!-- create gradient you want to use with the angle you want to use -->
<shape android:shape="rectangle" >
<gradient
android:angle="0"
android:startColor="@color/green_100"
android:endColor="@color/green_500" />
<corners android:radius="8dp" />
</shape>
</item>
<item
android:bottom="3dp"
android:left="3dp"
android:right="3dp"
android:top="3dp">
<shape
android:shape="rectangle"
android:layout_width="match_parent"
android:layout_height="match_parent">
<solid android:color="@color/content_background" />
<corners android:radius="6dp" />
</shape>
<ripple android:color="@color/white" />
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

@ -0,0 +1,120 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="110dp"
android:height="110dp"
android:viewportWidth="110"
android:viewportHeight="110">
<path
android:pathData="M76.1,18.83l-12.57,6.83l-4.4,9.32l16.97,-9.21l9.04,5.42l0,22l-13.26,18.69l-16.88,11.75l-16.88,-11.75l-13.26,-18.69l0,-22l9.04,-5.42l14.26,7.74l3.02,-5.3l-17.28,-9.38l-15.07,9.05l0,27.12l15.07,21.1l21.1,15.07l21.1,-15.07l15.07,-21.1l0,-27.12z"
android:fillColor="#C92B2B"/>
<path
android:pathData="M75.8,36.31l0.3,0l0,13.87l-10.85,15.07l-10.25,7.53l-10.25,-7.53l-10.85,-15.07l0,-13.87l0.3,0l8.31,4.46l-8.61,-15l-9.04,5.42l0,22l13.26,18.69l16.88,11.75l16.88,-11.75l13.26,-18.69l0,-22l-9.04,-5.42l-8.61,15z"
android:fillColor="#FF6165"/>
<path
android:pathData="M51.24,45.45l-4.68,-9.14l1.6,-2.8l-14.26,-7.74l8.61,15z"
android:fillColor="#FF6165"/>
<path
android:pathData="M51.24,45.45l-4.68,-9.14l1.6,-2.8l-14.26,-7.74l8.61,15z"
android:strokeAlpha="0.5"
android:fillColor="#FFFFFF"
android:fillAlpha="0.5"/>
<path
android:pathData="M60.87,44.32l6.62,-3.55l8.61,-15l-16.97,9.21l-1.31,2.78z"
android:fillColor="#FF6165"/>
<path
android:pathData="M60.87,44.32l6.62,-3.55l8.61,-15l-16.97,9.21l-1.31,2.78z"
android:strokeAlpha="0.25"
android:fillColor="#FFFFFF"
android:fillAlpha="0.25"/>
<path
android:pathData="M55,67.67l-4.22,-7.67l0.46,-4.03l-8.73,-15.2l-8.31,-4.46l-0.3,0l0,13.87l10.85,15.07l10.25,7.53l10.25,-7.53l10.85,-15.07l0,-13.87l-0.3,0l-8.31,4.46l-12.49,21.76z"
android:fillColor="#FF6165"/>
<path
android:pathData="M38.12,71.88l16.88,11.75l0,-10.85l-7.95,-5.84z"
android:strokeAlpha="0.35"
android:fillColor="#B52428"
android:fillAlpha="0.35"/>
<path
android:pathData="M33.9,36.31l0.3,0l8.31,4.46l-8.61,-15l-9.04,5.42l0,22l14.43,4.48l-5.39,-7.49z"
android:strokeAlpha="0.5"
android:fillColor="#B52428"
android:fillAlpha="0.5"/>
<path
android:pathData="M42.513,40.774l0,0l8.725,15.206l0,0z"
android:strokeAlpha="0.5"
android:fillColor="#FFFFFF"
android:fillAlpha="0.5"/>
<path
android:pathData="M51.6,61.48l-12.31,-3.81l5.46,7.58l2.3,1.69l5.79,-3.21z"
android:strokeAlpha="0.5"
android:fillColor="#FFFFFF"
android:fillAlpha="0.5"/>
<path
android:pathData="M52.84,63.73l-5.79,3.21l7.95,5.84l0,-5.11z"
android:strokeAlpha="0.35"
android:fillColor="#B52428"
android:fillAlpha="0.35"/>
<path
android:pathData="M52.84,63.73l-5.79,3.21l7.95,5.84l0,-5.11z"
android:strokeAlpha="0.5"
android:fillColor="#FFFFFF"
android:fillAlpha="0.5"/>
<path
android:pathData="M50.78,60l0.46,-4.03l-8.73,-15.2l-8.31,-4.46l-0.3,0l0,13.87l5.39,7.49l12.31,3.81z"
android:strokeAlpha="0.5"
android:fillColor="#B52428"
android:fillAlpha="0.5"/>
<path
android:pathData="M50.78,60l0.46,-4.03l-8.73,-15.2l-8.31,-4.46l-0.3,0l0,13.87l5.39,7.49l12.31,3.81z"
android:strokeAlpha="0.5"
android:fillColor="#FFFFFF"
android:fillAlpha="0.5"/>
<path
android:pathData="M52.21,47.34l-0.97,-1.89l-8.73,-4.68l8.73,15.2z"
android:fillColor="#FF6165"/>
<path
android:pathData="M52.21,47.34l-0.97,-1.89l-8.73,-4.68l8.73,15.2z"
android:strokeAlpha="0.5"
android:fillColor="#FFFFFF"
android:fillAlpha="0.5"/>
<path
android:pathData="M52.21,47.34l-0.97,-1.89l-8.73,-4.68l8.73,15.2z"
android:strokeAlpha="0.5"
android:fillColor="#FFFFFF"
android:fillAlpha="0.5"/>
<path
android:pathData="M55,60l0,2.53l12.49,-21.76l-6.62,3.55l0.1,0.2z"
android:fillColor="#FF6165"/>
<path
android:pathData="M55,60l0,2.53l12.49,-21.76l-6.62,3.55l0.1,0.2z"
android:strokeAlpha="0.25"
android:fillColor="#FFFFFF"
android:fillAlpha="0.25"/>
<path
android:pathData="M55,60l0,2.53l12.49,-21.76l-6.62,3.55l0.1,0.2z"
android:strokeAlpha="0.5"
android:fillColor="#FFFFFF"
android:fillAlpha="0.5"/>
<path
android:pathData="M55,62.53l16.88,9.35l-16.88,11.75z"
android:strokeAlpha="0.5"
android:fillColor="#FFFFFF"
android:fillType="evenOdd"
android:fillAlpha="0.5"/>
<path
android:pathData="M71.88,71.88l13.26,-18.69l-30.14,9.34z"
android:strokeAlpha="0.5"
android:fillColor="#B52428"
android:fillType="evenOdd"
android:fillAlpha="0.5"/>
<path
android:pathData="M55,62.53l21.1,-36.76l9.04,5.42l0,22z"
android:strokeAlpha="0.35"
android:fillColor="#B52428"
android:fillType="evenOdd"
android:fillAlpha="0.35"/>
<path
android:pathData="M75.8,36.31l-8.31,4.46l-12.49,21.76l0,5.14l0,5.11l10.25,-7.53l10.85,-15.07l0,-13.87z"
android:strokeAlpha="0.5"
android:fillColor="#FFFFFF"
android:fillAlpha="0.5"/>
</vector>

View file

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:background="@color/content_background"
android:orientation="vertical"
android:gravity="center_horizontal"
android:paddingHorizontal="23dp">
<androidx.legacy.widget.Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="157dp">
<ImageView
android:id="@+id/ghost_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/death_ghost"
android:layout_centerHorizontal="true" />
<ImageView
android:layout_width="110dp"
android:layout_height="110dp"
android:src="@drawable/ic_broken_heart"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Title2"
android:textStyle="bold"
android:text="@string/you_ran_out_of_health"
android:layout_marginHorizontal="@dimen/spacing_xlarge"
android:gravity="center"/>
<TextView
android:id="@+id/loss_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/spacing_xlarge"
android:layout_marginVertical="@dimen/spacing_medium"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:lineSpacingExtra="4dp"
android:gravity="center" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/death_description"
android:textSize="20sp"
android:lineSpacingExtra="4dp"
android:textColor="@color/text_secondary"
android:layout_marginHorizontal="@dimen/spacing_xlarge"
android:gravity="center"/>
<androidx.legacy.widget.Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
<Button
android:id="@+id/restart_button"
android:layout_width="match_parent"
android:layout_height="69dp"
android:text="@string/faint_button"
android:textStyle="bold"
style="@style/HabiticaButton.Maroon"
android:layout_marginBottom="6dp"/>
<com.habitrpg.android.habitica.ui.views.ads.AdButton
android:id="@+id/ad_button"
android:layout_width="match_parent"
app:text="@string/watch_ad_to_open"
app:activeBackground="@drawable/ad_button_background_content"
android:layout_height="60dp"
app:textColor="@color/text_primary"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/text_quad"
android:text="@string/faint_broken_equipment"
android:layout_margin="@dimen/spacing_large"/>
</LinearLayout>

View file

@ -82,6 +82,7 @@
style="@style/HabitButton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/selection_highlight"
android:contentDescription="@string/negative_habit_form" />
</FrameLayout>
</LinearLayout>

View file

@ -164,6 +164,11 @@
android:name="value"
app:argType="string" />
</activity>
<activity
android:id="@+id/deathActivity"
android:name="com.habitrpg.android.habitica.ui.activities.DeathActivity"
android:label="@string/faint_header" >
</activity>
<activity
android:id="@+id/subscriptionPurchaseActivity"
android:name="com.habitrpg.android.habitica.ui.activities.GemPurchaseActivity"

View file

@ -160,6 +160,8 @@
<declare-styleable name="AdButton">
<attr name="text" />
<attr name="currency" />
<attr name="activeBackground" format="integer" />
<attr name="textColor" format="color" />
</declare-styleable>
<declare-styleable name="SparkView">
<attr name="color" />

View file

@ -1256,7 +1256,7 @@
<string name="oldest">Oldest</string>
<string name="sort_by">Sort By</string>
<string name="january">January</string>
<string name="febuary">Febuary</string>
<string name="febuary">February</string>
<string name="march">March</string>
<string name="april">April</string>
<string name="may">May</string>
@ -1270,4 +1270,8 @@
<string name="cds_subtitle">Adjust when your day switches over past the default time of midnight.</string>
<string name="buy_set">Buy Set</string>
<string name="tutorial_reset_confirmation">Your Tutorials were reset</string>
<string name="you_ran_out_of_health">You ran out of Health!</string>
<string name="death_description">But you can get them all back with hard work! Good luck—youll do great.</string>
<string name="faint_broken_equipment">Broken equipment can be repurchased from Rewards</string>
<string name="faint_loss_description"><![CDATA[Youll drop to level <b>%d</b>, lose <b>%d</b> Gold, and break <b>a piece of gear</b>…]]></string>
</resources>

View file

@ -692,6 +692,14 @@
<item name="android:backgroundTint">@color/red_100</item>
</style>
<style name="HabiticaButton.Maroon" parent="HabiticaButton">
<item name="android:backgroundTint">@color/maroon_100</item>
</style>
<style name="HabiticaButton.Maroon.Small" parent="HabiticaButton.Small">
<item name="android:backgroundTint">@color/maroon_100</item>
</style>
<style name="SegmentTitle" parent="Headline">
<item name="android:layout_marginBottom">@dimen/spacing_medium</item>
</style>

View file

@ -16,6 +16,7 @@ import com.habitrpg.android.habitica.ui.activities.AdventureGuideActivity;
import com.habitrpg.android.habitica.ui.activities.ArmoireActivity;
import com.habitrpg.android.habitica.ui.activities.ChallengeFormActivity;
import com.habitrpg.android.habitica.ui.activities.ClassSelectionActivity;
import com.habitrpg.android.habitica.ui.activities.DeathActivity;
import com.habitrpg.android.habitica.ui.activities.FixCharacterValuesActivity;
import com.habitrpg.android.habitica.ui.activities.FullProfileActivity;
import com.habitrpg.android.habitica.ui.activities.GemPurchaseActivity;
@ -105,6 +106,7 @@ 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.StableViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment.EquipmentOverviewViewModel;
import com.habitrpg.android.habitica.ui.views.dialogs.PetSuggestHatchDialog;
@ -361,4 +363,8 @@ public interface UserComponent {
void inject(@NotNull ArmoireActivity armoireActivity);
void inject(@NotNull TasksViewModel tasksViewModel);
void inject(@NotNull StableViewModel stableViewModel);
void inject(@NotNull DeathActivity deathActivity);
}

View file

@ -0,0 +1,86 @@
package com.habitrpg.android.habitica.ui.activities
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.ActivityDeathBinding
import com.habitrpg.android.habitica.extensions.fromHtml
import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.helpers.AdHandler
import com.habitrpg.android.habitica.helpers.AdType
import com.habitrpg.android.habitica.helpers.Animations
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.ads.AdButton
import javax.inject.Inject
class DeathActivity: BaseActivity() {
private lateinit var binding: ActivityDeathBinding
@Inject
internal lateinit var inventoryRepository: InventoryRepository
@Inject
internal lateinit var appConfigManager: AppConfigManager
@Inject
lateinit var userViewModel: MainUserViewModel
override fun getLayoutResId(): Int = R.layout.activity_armoire
override fun injectActivity(component: UserComponent?) {
component?.inject(this)
}
override fun getContentView(): View {
binding = ActivityDeathBinding.inflate(layoutInflater)
return binding.root
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.ghostView.startAnimation(Animations.bobbingAnimation())
userViewModel.user.observeOnce(this) { user ->
binding.lossDescription.text = getString(R.string.faint_loss_description, (user?.stats?.lvl ?: 2).toInt() - 1, user?.stats?.gp?.toInt()).fromHtml()
}
if (appConfigManager.enableArmoireAds()) {
val handler = AdHandler(this, AdType.FAINT) {
if (!it) {
return@AdHandler
}
Log.d("AdHandler", "Reviving user")
compositeSubscription.add(
userRepository.updateUser("stats.hp", 1).subscribe({
finish()
}, RxErrorHandler.handleEmptyError())
)
}
handler.prepare {
if (it && binding.adButton.state == AdButton.State.LOADING) {
binding.adButton.state = AdButton.State.READY
} else if (!it) {
binding.adButton.visibility = View.INVISIBLE
}
}
binding.adButton.updateForAdType(AdType.ARMOIRE, lifecycleScope)
binding.adButton.setOnClickListener {
binding.adButton.state = AdButton.State.LOADING
handler.show()
}
} else {
binding.adButton.visibility = View.GONE
}
binding.restartButton.setOnClickListener {
binding.restartButton.isEnabled = false
userRepository.revive().subscribe({
finish()
}, RxErrorHandler.handleEmptyError())
}
}
}

View file

@ -9,7 +9,6 @@ import android.content.pm.PackageManager
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import android.view.MenuItem
import android.view.View
@ -30,7 +29,6 @@ import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.local.UserQuestStatus
import com.habitrpg.android.habitica.databinding.ActivityMainBinding
import com.habitrpg.android.habitica.databinding.DialogFaintBinding
import com.habitrpg.android.habitica.extensions.dpToPx
import com.habitrpg.android.habitica.extensions.getThemeColor
import com.habitrpg.android.habitica.extensions.hideKeyboard
@ -38,8 +36,6 @@ import com.habitrpg.android.habitica.extensions.isUsingNightModeResources
import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.extensions.updateStatusBarColor
import com.habitrpg.android.habitica.helpers.AdHandler
import com.habitrpg.android.habitica.helpers.AdType
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.MainNavigationController
@ -59,22 +55,20 @@ import com.habitrpg.android.habitica.ui.TutorialView
import com.habitrpg.android.habitica.ui.fragments.NavigationDrawerFragment
import com.habitrpg.android.habitica.ui.viewmodels.MainActivityViewModel
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.SnackbarActivity
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.dialogs.QuestCompletedDialog
import com.habitrpg.android.habitica.ui.views.yesterdailies.YesterdailyDialog
import com.habitrpg.android.habitica.widget.AvatarStatsWidgetProvider
import com.habitrpg.android.habitica.widget.DailiesWidgetProvider
import com.habitrpg.android.habitica.widget.HabitButtonWidgetProvider
import com.habitrpg.android.habitica.widget.TodoListWidgetProvider
import javax.inject.Inject
import kotlin.time.DurationUnit
import kotlin.time.toDuration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.time.DurationUnit
import kotlin.time.toDuration
open class MainActivity : BaseActivity(), SnackbarActivity {
private var launchScreen: String? = null
@ -104,7 +98,6 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
private var avatarInHeader: AvatarWithBarsViewModel? = null
val notificationsViewModel: NotificationsViewModel by viewModels()
val viewModel: MainActivityViewModel by viewModels()
private var faintDialog: HabiticaAlertDialog? = null
private var sideAvatarView: AvatarView? = null
private var drawerFragment: NavigationDrawerFragment? = null
var drawerToggle: ActionBarDrawerToggle? = null
@ -456,36 +449,8 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
return
}
if (this.faintDialog == null && !this.isFinishing) {
val binding = DialogFaintBinding.inflate(this.layoutInflater)
binding.hpBar.setLightBackground(true)
binding.hpBar.setIcon(HabiticaIconsHelper.imageOfHeartLightBg())
viewModel.user.value?.let { binding.avatarView.setAvatar(it) }
this.faintDialog = HabiticaAlertDialog(this)
faintDialog?.setTitle(R.string.faint_header)
faintDialog?.setAdditionalContentView(binding.root)
faintDialog?.addButton(R.string.faint_button, true) { _, _ ->
faintDialog = null
userRepository.revive().subscribe({ }, RxErrorHandler.handleEmptyError())
}
if (AdHandler.isAllowed(AdType.FAINT)) {
val handler = AdHandler(this, AdType.FAINT) {
Log.d("AdHandler", "Reviving user")
compositeSubscription.add(
userRepository.updateUser("stats.hp", 50)
.subscribe({}, RxErrorHandler.handleEmptyError())
)
}
handler.prepare()
faintDialog?.addButton(R.string.watch_ad_to_revive, true) { _, _ ->
faintDialog = null
handler.show()
}
}
soundManager.loadAndPlayAudio(SoundManager.SoundDeath)
this.faintDialog?.enqueue()
if (!this.isFinishing) {
MainNavigationController.navigate(R.id.deathActivity)
}
}

View file

@ -4,17 +4,21 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayoutMediator
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.databinding.FragmentViewpagerBinding
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.viewmodels.StableViewModel
class StableFragment : BaseMainFragment<FragmentViewpagerBinding>() {
override var binding: FragmentViewpagerBinding? = null
private val viewModel: StableViewModel by viewModels()
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentViewpagerBinding {
return FragmentViewpagerBinding.inflate(inflater, container, false)
}

View file

@ -1,40 +1,40 @@
package com.habitrpg.android.habitica.ui.fragments.inventory.stable
import android.app.Application
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.extensions.getTranslatedType
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.inventory.Animal
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.HatchingPotion
import com.habitrpg.android.habitica.models.inventory.StableSection
import com.habitrpg.android.habitica.models.user.OwnedMount
import com.habitrpg.android.habitica.models.user.OwnedObject
import com.habitrpg.android.habitica.models.user.OwnedPet
import com.habitrpg.android.habitica.ui.adapter.inventory.StableRecyclerAdapter
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 com.habitrpg.android.habitica.ui.viewmodels.StableViewModel
import com.habitrpg.android.habitica.ui.viewmodels.StableViewModelFactory
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.kotlin.combineLatest
import javax.inject.Inject
class StableRecyclerFragment :
BaseFragment<FragmentRefreshRecyclerviewBinding>(),
SwipeRefreshLayout.OnRefreshListener {
private val viewModel: StableViewModel by viewModels(factoryProducer = {
StableViewModelFactory(context?.applicationContext as? Application, itemType)
})
@Inject
lateinit var inventoryRepository: InventoryRepository
@Inject
@ -106,11 +106,10 @@ class StableRecyclerFragment :
adapter?.animalIngredientsRetriever = { animal, callback ->
Maybe.zip(
inventoryRepository.getItems(Egg::class.java, arrayOf(animal.animal)).firstElement(),
inventoryRepository.getItems(HatchingPotion::class.java, arrayOf(animal.color)).firstElement(),
{ eggs, potions ->
Pair(eggs.first() as? Egg, potions.first() as? HatchingPotion)
}
).subscribe(
inventoryRepository.getItems(HatchingPotion::class.java, arrayOf(animal.color)).firstElement()
) { eggs, potions ->
Pair(eggs.first() as? Egg, potions.first() as? HatchingPotion)
}.subscribe(
{
callback(it)
},
@ -159,118 +158,24 @@ class StableRecyclerFragment :
}
private fun loadItems() {
val observable: Maybe<out List<Animal>> = if ("pets" == itemType) {
inventoryRepository.getPets().firstElement()
} else {
inventoryRepository.getMounts().firstElement()
viewModel.items.observe(viewLifecycleOwner) {
adapter?.setItemList(it)
}
val ownedObservable: Flowable<out Map<String, OwnedObject>> = if ("pets" == itemType) {
inventoryRepository.getOwnedPets()
} else {
inventoryRepository.getOwnedMounts()
}.map {
val animalMap = mutableMapOf<String, OwnedObject>()
it.forEach { animal ->
val castedAnimal = animal as? OwnedObject ?: return@forEach
animalMap[castedAnimal.key ?: ""] = castedAnimal
}
animalMap
viewModel.eggs.observe(viewLifecycleOwner) {
adapter?.setEggs(it)
}
viewModel.ownedItems.observe(viewLifecycleOwner) {
adapter?.setOwnedItems(it)
}
viewModel.mounts.observe(viewLifecycleOwner) {
adapter?.setExistingMounts(it)
}
viewModel.ownedMounts.observe(viewLifecycleOwner) {
adapter?.setOwnedMounts(it)
}
compositeSubscription.add(
inventoryRepository.getItems(Egg::class.java)
.map {
val eggMap = mutableMapOf<String, Egg>()
it.forEach { egg ->
eggMap[egg.key] = egg as Egg
}
eggMap
}
.subscribe(
{
adapter?.setEggs(it)
},
RxErrorHandler.handleEmptyError()
)
)
compositeSubscription.add(
ownedObservable.combineLatest(observable.toFlowable())
.map { (ownedAnimals, unsortedAnimals) ->
mapAnimals(unsortedAnimals, ownedAnimals)
}
.subscribe({ items -> adapter?.setItemList(items) }, RxErrorHandler.handleEmptyError())
)
compositeSubscription.add(inventoryRepository.getOwnedItems(true).subscribe({ adapter?.setOwnedItems(it) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(inventoryRepository.getMounts().subscribe({ adapter?.setExistingMounts(it) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(
inventoryRepository.getOwnedMounts()
.map { ownedMounts ->
val mountMap = mutableMapOf<String, OwnedMount>()
ownedMounts.forEach { mountMap[it.key ?: ""] = it }
return@map mountMap
}
.subscribe({ adapter?.setOwnedMounts(it) }, RxErrorHandler.handleEmptyError())
)
}
private fun mapAnimals(unsortedAnimals: List<Animal>, ownedAnimals: Map<String, OwnedObject>): ArrayList<Any> {
val items = ArrayList<Any>()
var lastAnimal: Animal = unsortedAnimals.firstOrNull() ?: return items
var lastSection: StableSection? = null
for (animal in unsortedAnimals) {
val identifier = if (animal.animal.isNotEmpty() && (animal.type != "special" && animal.type != "wacky")) animal.animal else animal.key
val lastIdentifier = if (lastAnimal.animal.isNotEmpty()) lastAnimal.animal else lastAnimal.key
if (animal.type == "premium") {
if (!items.contains(lastAnimal)) {
items.add(lastAnimal)
}
lastAnimal = items.first { (it as? Animal)?.animal == animal.animal } as Animal
} else if (identifier != lastIdentifier || animal === unsortedAnimals[unsortedAnimals.size - 1]) {
if (!((lastAnimal.type == "special") && lastAnimal.numberOwned == 0) && !items.contains(lastAnimal)) {
items.add(lastAnimal)
}
lastAnimal = animal
}
if (animal.type != lastSection?.key && animal.type != "premium") {
if (items.size > 0 && items[items.size - 1].javaClass == StableSection::class.java) {
items.removeAt(items.size - 1)
}
val title = if (itemType == "pets") {
context?.getString(R.string.pet_category, animal.getTranslatedType(context))
} else {
context?.getString(R.string.mount_category, animal.getTranslatedType(context))
}
val section = StableSection(animal.type, title ?: "")
items.add(section)
lastSection = section
}
val isOwned = when (itemType) {
"pets" -> {
val ownedPet = ownedAnimals[animal.key] as? OwnedPet
ownedPet?.trained ?: 0 > 0
}
"mounts" -> {
val ownedMount = ownedAnimals[animal.key] as? OwnedMount
ownedMount?.owned == true
}
else -> false
}
lastAnimal.totalNumber += 1
lastSection?.totalCount = (lastSection?.totalCount ?: 0) + 1
if (isOwned) {
lastAnimal.numberOwned += 1
lastSection?.ownedCount = (lastSection?.ownedCount ?: 0) + 1
}
}
if (!((lastAnimal.type == "premium" || lastAnimal.type == "special") && lastAnimal.numberOwned == 0)) {
items.add(lastAnimal)
}
items.add(0, "header")
items.removeAll { it is StableSection && (it.key as? String) == "special" && it.ownedCount == 0 }
return items
}
companion object {
private const val ITEM_TYPE_KEY = "CLASS_TYPE_KEY"

View file

@ -0,0 +1,173 @@
package com.habitrpg.android.habitica.ui.viewmodels
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.extensions.getTranslatedType
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.inventory.Animal
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.Mount
import com.habitrpg.android.habitica.models.inventory.StableSection
import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.models.user.OwnedMount
import com.habitrpg.android.habitica.models.user.OwnedObject
import com.habitrpg.android.habitica.models.user.OwnedPet
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.kotlin.combineLatest
import javax.inject.Inject
class StableViewModel(private val application: Application?, private val itemType: String?): BaseViewModel() {
@Inject
lateinit var inventoryRepository: InventoryRepository
override fun inject(component: UserComponent) {
component.inject(this)
}
init {
loadItems()
}
private val _items: MutableLiveData<List<Any>> = MutableLiveData()
val items: LiveData<List<Any>> = _items
private val _eggs: MutableLiveData<Map<String, Egg>> = MutableLiveData()
val eggs: LiveData<Map<String, Egg>> = _eggs
private val _ownedItems: MutableLiveData<Map<String, OwnedItem>> = MutableLiveData()
val ownedItems: LiveData<Map<String, OwnedItem>> = _ownedItems
private val _mounts: MutableLiveData<List<Mount>> = MutableLiveData()
val mounts: LiveData<List<Mount>> = _mounts
private val _ownedMounts: MutableLiveData<Map<String, OwnedMount>> = MutableLiveData()
val ownedMounts: LiveData<Map<String, OwnedMount>> = _ownedMounts
private fun loadItems() {
val observable: Maybe<out List<Animal>> = if ("pets" == itemType) {
inventoryRepository.getPets().firstElement()
} else {
inventoryRepository.getMounts().firstElement()
}
val ownedObservable: Flowable<out Map<String, OwnedObject>> = if ("pets" == itemType) {
inventoryRepository.getOwnedPets()
} else {
inventoryRepository.getOwnedMounts()
}.map {
val animalMap = mutableMapOf<String, OwnedObject>()
it.forEach { animal ->
val castedAnimal = animal as? OwnedObject ?: return@forEach
animalMap[castedAnimal.key ?: ""] = castedAnimal
}
animalMap
}
disposable.add(
inventoryRepository.getItems(Egg::class.java)
.map {
val eggMap = mutableMapOf<String, Egg>()
it.forEach { egg ->
eggMap[egg.key] = egg as Egg
}
eggMap
}
.subscribe(
{
_eggs.value = it
},
RxErrorHandler.handleEmptyError()
)
)
disposable.add(
ownedObservable.combineLatest(observable.toFlowable())
.map { (ownedAnimals, unsortedAnimals) ->
mapAnimals(unsortedAnimals, ownedAnimals)
}
.subscribe({ _items.value = it }, RxErrorHandler.handleEmptyError())
)
disposable.add(inventoryRepository.getOwnedItems(true).subscribe({ _ownedItems.value = it }, RxErrorHandler.handleEmptyError()))
disposable.add(inventoryRepository.getMounts().subscribe({ _mounts.value = it }, RxErrorHandler.handleEmptyError()))
disposable.add(
inventoryRepository.getOwnedMounts()
.map { ownedMounts ->
val mountMap = mutableMapOf<String, OwnedMount>()
ownedMounts.forEach { mountMap[it.key ?: ""] = it }
return@map mountMap
}
.subscribe({ _ownedMounts.value = it }, RxErrorHandler.handleEmptyError())
)
}
private fun mapAnimals(unsortedAnimals: List<Animal>, ownedAnimals: Map<String, OwnedObject>): ArrayList<Any> {
val items = ArrayList<Any>()
var lastAnimal: Animal = unsortedAnimals.firstOrNull() ?: return items
var lastSection: StableSection? = null
for (animal in unsortedAnimals) {
val identifier = if (animal.animal.isNotEmpty() && (animal.type != "special" && animal.type != "wacky")) animal.animal else animal.key
val lastIdentifier = if (lastAnimal.animal.isNotEmpty()) lastAnimal.animal else lastAnimal.key
if (animal.type == "premium") {
if (!items.contains(lastAnimal)) {
items.add(lastAnimal)
}
lastAnimal = items.first { (it as? Animal)?.animal == animal.animal } as Animal
} else if (identifier != lastIdentifier || animal === unsortedAnimals[unsortedAnimals.size - 1]) {
if (!((lastAnimal.type == "special") && lastAnimal.numberOwned == 0) && !items.contains(lastAnimal)) {
items.add(lastAnimal)
}
lastAnimal = animal
}
if (animal.type != lastSection?.key && animal.type != "premium") {
if (items.size > 0 && items[items.size - 1].javaClass == StableSection::class.java) {
items.removeAt(items.size - 1)
}
val title = if (itemType == "pets") {
application?.getString(R.string.pet_category, animal.getTranslatedType(application))
} else {
application?.getString(R.string.mount_category, animal.getTranslatedType(application))
}
val section = StableSection(animal.type, title ?: "")
items.add(section)
lastSection = section
}
val isOwned = when (itemType) {
"pets" -> {
val ownedPet = ownedAnimals[animal.key] as? OwnedPet
ownedPet?.trained ?: 0 > 0
}
"mounts" -> {
val ownedMount = ownedAnimals[animal.key] as? OwnedMount
ownedMount?.owned == true
}
else -> false
}
lastAnimal.totalNumber += 1
lastSection?.totalCount = (lastSection?.totalCount ?: 0) + 1
if (isOwned) {
lastAnimal.numberOwned += 1
lastSection?.ownedCount = (lastSection?.ownedCount ?: 0) + 1
}
}
if (!((lastAnimal.type == "premium" || lastAnimal.type == "special") && lastAnimal.numberOwned == 0)) {
items.add(lastAnimal)
}
items.add(0, "header")
items.removeAll { it is StableSection && (it.key as? String) == "special" && it.ownedCount == 0 }
return items
}
}
class StableViewModelFactory(
private val application: Application?,
private val itemType: String?
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return StableViewModel(application, itemType) as T
}
}

View file

@ -3,6 +3,7 @@ package com.habitrpg.android.habitica.ui.views.ads
import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.view.View
import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleCoroutineScope
@ -13,13 +14,13 @@ import com.habitrpg.android.habitica.extensions.getShortRemainingString
import com.habitrpg.android.habitica.extensions.layoutInflater
import com.habitrpg.android.habitica.helpers.AdHandler
import com.habitrpg.android.habitica.helpers.AdType
import java.util.Date
import kotlin.time.DurationUnit
import kotlin.time.toDuration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.Date
import kotlin.time.DurationUnit
import kotlin.time.toDuration
class AdButton @JvmOverloads constructor(
context: Context,
@ -42,6 +43,8 @@ class AdButton @JvmOverloads constructor(
private var nextAdDate: Date? = null
private val binding = AdButtonBinding.inflate(context.layoutInflater, this)
private var activeBackgroundRes: Int = R.drawable.ad_button_background
var text: String = ""
set(value) {
field = value
@ -56,10 +59,14 @@ class AdButton @JvmOverloads constructor(
)?.let { attributes ->
text = attributes.getString(R.styleable.AdButton_text) ?: ""
binding.currencyView.currency = attributes.getString(R.styleable.AdButton_currency)
activeBackgroundRes = attributes.getResourceId(R.styleable.AdButton_activeBackground, R.drawable.ad_button_background)
binding.textView.setTextColor(attributes.getColor(R.styleable.AdButton_textColor, ContextCompat.getColor(context, R.color.white)))
}
binding.textView.setTextColor(ContextCompat.getColor(context, R.color.white))
binding.currencyView.setTextColor(ContextCompat.getColor(context, R.color.white))
binding.currencyView.value = 0.0
if (binding.currencyView.currency?.isNotBlank() != true) {
binding.currencyView.visibility = View.GONE
}
gravity = Gravity.CENTER
state = State.READY
}
@ -72,7 +79,7 @@ class AdButton @JvmOverloads constructor(
binding.textView.alpha = 1.0f
binding.textView.visibility = VISIBLE
binding.currencyView.visibility = VISIBLE
setBackgroundResource(R.drawable.ad_button_background)
setBackgroundResource(activeBackgroundRes)
}
State.UNAVAILABLE -> {
binding.loadingIndicator.visibility = GONE