Implement new armoire screen

This commit is contained in:
Phillip Thelen 2022-04-19 17:17:45 +02:00
parent ce0fb93fc6
commit c1b29a547a
28 changed files with 361 additions and 19 deletions

View file

@ -70,6 +70,13 @@
android:pathPattern="/settings/.*"/>
</intent-filter>
</activity>
<activity
android:name=".ui.activities.ArmoireActivity"
android:parentActivityName=".ui.activities.MainActivity"
android:label="@string/armoire"
android:screenOrientation="unspecified"
tools:ignore="UnusedAttribute">
</activity>
<activity
android:name=".ui.activities.NotificationsActivity"
android:parentActivityName=".ui.activities.MainActivity"

View file

@ -15,7 +15,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath 'com.android.tools.build:gradle:7.1.3'
classpath 'net.sourceforge.pmd:pmd-java:5.5.3'
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/yellow_500"/>
<corners android:radius="20dip"/>
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
</shape>

View file

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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">
<FrameLayout
android:id="@+id/confetti_anchor"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal"
android:paddingTop="19dp">
<com.habitrpg.android.habitica.ui.views.CurrencyView
android:id="@+id/gold_view"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:background="@drawable/pill_bg_yellow_500"
android:gravity="center"
android:paddingStart="6dp"
android:paddingEnd="8dp"
android:textStyle="bold"
android:textSize="20sp"
tools:text="118"/>
<FrameLayout
android:layout_width="165dp"
android:layout_height="158dp"
android:background="@drawable/armoire_circle"
android:layout_marginTop="23dp"
android:layout_marginBottom="23dp">
<ImageView
android:id="@+id/icon_view"
android:layout_width="@dimen/shopitem_image_size"
android:layout_height="@dimen/shopitem_image_size"
android:layout_gravity="center"/>
</FrameLayout>
<TextView
android:id="@+id/title_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="+21 Experience"
android:textStyle="bold"
android:textSize="28sp"
android:textColor="@color/text_primary"/>
<TextView
android:id="@+id/subtitle_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="You wrestle with the Armoire and gain Experience. Take that!"
android:layout_marginHorizontal="50dp"
android:gravity="center_horizontal"
android:layout_marginTop="8dp"
android:textSize="20sp"
/>
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/armoire_background"
android:orientation="vertical"
android:gravity="center"
android:padding="12dp">
<TextView
android:id="@+id/equipment_count_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/equipment_remaining"
android:textColor="@color/white"
style="@style/Headline"
android:textStyle="bold"/>
<TextView
android:id="@+id/no_equipment_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/new_pieces_added_every_month"
style="@style/Body2"
android:textColor="@color/white"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">
<Button
android:id="@+id/equip_button"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="60dp"
android:text="@string/equip"
android:textStyle="bold"
style="@style/HabiticaButton.White"/>
<Space
android:id="@+id/button_spacer"
android:layout_width="12dp"
android:layout_height="wrap_content" />
<Button
android:id="@+id/close_button"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="60dp"
android:text="@string/close"
android:textStyle="bold"
style="@style/HabiticaButton.White"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/armoire_drop_rates"
android:textColor="@color/brand_500"
android:layout_marginTop="12dp"
style="@style/Body2"/>
</LinearLayout>
</LinearLayout>
</FrameLayout>

View file

@ -6,7 +6,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/RowWrapper"
android:id="@+id/gear_container">
android:id="@+id/gear_container"
android:gravity="center_vertical">
<FrameLayout
android:id="@+id/gear_icon_background_view"
@ -16,14 +17,14 @@
<ImageView
android:layout_width="@dimen/gear_image_size"
android:layout_height="@dimen/gear_image_size"
android:layout_gravity="center"
android:id="@+id/gear_image"/>
</FrameLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical">
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"

View file

@ -142,6 +142,20 @@
android:id="@+id/openMountDetail"
app:destination="@id/mountDetailRecyclerFragment" />
</fragment>
<activity
android:id="@+id/armoireActivity"
android:name="com.habitrpg.android.habitica.ui.activities.ArmoireActivity"
android:label="@string/armoire" >
<argument
android:name="type"
app:argType="string" />
<argument
android:name="text"
app:argType="string" />
<argument
android:name="key"
app:argType="string" />
</activity>
<activity
android:id="@+id/subscriptionPurchaseActivity"
android:name="com.habitrpg.android.habitica.ui.activities.GemPurchaseActivity"
@ -399,6 +413,9 @@
<action
android:id="@+id/openProfileActivity"
app:destination="@id/fullProfileActivity" />
<action
android:id="@+id/openArmoireActivity"
app:destination="@id/armoireActivity" />
<fragment
android:id="@+id/achievementsFragment"
android:name="com.habitrpg.android.habitica.ui.fragments.AchievementsFragment"

View file

@ -276,8 +276,8 @@
<string name="mounts">Mounts</string>
<string name="quest_items_found">You\'ve found %d quest items</string>
<string name="armoireEquipment">You found %s in the Armoire</string>
<string name="armoireFood">You rummage in the Armoire and find%1$s %2$s. What\'s that doing in here?</string>
<string name="armoireEquipment_new">You find a piece of rare Equipment in the Armoire!</string>
<string name="armoireFood_new">You rummage in the Armoire and find food. What\'s that doing in here?</string>
<string name="armoireExp">You wrestle with the Armoire and gain Experience. Take that!</string>
<string name="sell">Sell (%d Gold)</string>
<string name="hatch_with_potion">Hatch with potion</string>
@ -1225,5 +1225,9 @@
<string name="compact">Compact</string>
<string name="minimal">Minimal</string>
<string name="armoire_drop_rates">Armoire drop rates</string>
<string name="armoire">Armoire</string>
<string name="equipment_remaining">Equipment Remaining: %d</string>
<string name="new_pieces_added_every_month">New pieces added every month</string>
<string name="watch_ad">Watch Ad</string>
</resources>

View file

@ -705,6 +705,11 @@
<item name="android:backgroundTint">@color/yellow_100</item>
</style>
<style name="HabiticaButton.White" parent="HabiticaButton">
<item name="android:backgroundTint">@color/white</item>
<item name="android:textColor">@color/brand_400</item>
</style>
<style name="CurrencyTextView">
<item name="android:textSize">12sp</item>
<item name="android:layout_marginLeft">4dp</item>

View file

@ -13,6 +13,7 @@ import com.habitrpg.android.habitica.receivers.NotificationPublisher;
import com.habitrpg.android.habitica.receivers.TaskAlarmBootReceiver;
import com.habitrpg.android.habitica.receivers.TaskReceiver;
import com.habitrpg.android.habitica.ui.activities.AdventureGuideActivity;
import com.habitrpg.android.habitica.ui.activities.ArmoireActivity;
import com.habitrpg.android.habitica.ui.activities.ChallengeFormActivity;
import com.habitrpg.android.habitica.ui.activities.ClassSelectionActivity;
import com.habitrpg.android.habitica.ui.activities.FixCharacterValuesActivity;
@ -358,4 +359,6 @@ public interface UserComponent {
void inject(@NotNull MainUserViewModel mainUserViewModel);
void inject(@NotNull PetSuggestHatchDialog petSuggestHatchDialog);
void inject(@NotNull ArmoireActivity armoireActivity);
}

View file

@ -7,10 +7,10 @@ import java.text.DecimalFormat
object NumberAbbreviator {
fun abbreviate(context: Context, number: Double, numberOfDecimals: Int = 2): String {
fun abbreviate(context: Context, number: Double, numberOfDecimals: Int = 2, minForAbbrevation: Int = 0): String {
var usedNumber = number
var counter = 0
while (usedNumber >= 1000) {
while (usedNumber >= 1000 && number >= minForAbbrevation) {
counter++
usedNumber /= 1000
}

View file

@ -0,0 +1,131 @@
package com.habitrpg.android.habitica.ui.activities
import android.os.Bundle
import android.view.Gravity
import android.view.View
import android.view.animation.AccelerateInterpolator
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.ActivityArmoireBinding
import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.ui.helpers.loadImage
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.plattysoft.leonids.ParticleSystem
import javax.inject.Inject
class ArmoireActivity: BaseActivity() {
private var equipmentKey: String? = null
private var gold: Double? = null
private var hasAnimatedChanges: Boolean = false
private lateinit var binding: ActivityArmoireBinding
@Inject
internal lateinit var inventoryRepository: InventoryRepository
@Inject
internal lateinit var appConfigManager: AppConfigManager
@Inject
lateinit var userViewModel: MainUserViewModel
override fun getLayoutResId(): Int = R.layout.activity_armoire
override fun injectActivity(component: UserComponent?) {
component?.inject(this)
}
override fun getContentView(): View {
binding = ActivityArmoireBinding.inflate(layoutInflater)
return binding.root
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.goldView.currency = "gold"
binding.goldView.animationDuration = 1000
binding.goldView.animationDelay = 500
binding.goldView.minForAbbrevation = 1000000
binding.goldView.decimals = 0
userViewModel.user.observeOnce(this) { user ->
gold = user?.stats?.gp
val remaining = inventoryRepository.getArmoireRemainingCount()
binding.equipmentCountView.text = getString(R.string.equipment_remaining, remaining)
binding.noEquipmentView.visibility = if (remaining > 0) View.GONE else View.VISIBLE
}
binding.closeButton.setOnClickListener {
finish()
}
binding.equipButton.setOnClickListener {
equipmentKey?.let { it1 -> inventoryRepository.equip("gear", it1).subscribe() }
finish()
}
intent.extras?.let {
val args = ArmoireActivityArgs.fromBundle(it)
equipmentKey = args.key
configure(args.type, args.key, args.text)
}
}
override fun onResume() {
super.onResume()
startAnimation()
}
private fun startAnimation() {
val gold = gold?.toInt()
if (hasAnimatedChanges || gold == null) return
binding.goldView.value = (gold).toDouble()
binding.goldView.value = (gold - 100).toDouble()
val container = binding.confettiAnchor
container.postDelayed(
{
createParticles(container, R.drawable.confetti_blue)
createParticles(container, R.drawable.confetti_red)
createParticles(container, R.drawable.confetti_yellow)
createParticles(container, R.drawable.confetti_purple)
},
500
)
hasAnimatedChanges = true
}
private fun createParticles(container: FrameLayout, resource: Int) {
ParticleSystem(
container,
30,
ContextCompat.getDrawable(this, resource),
6000
)
.setRotationSpeed(144f)
.setScaleRange(1.0f, 1.6f)
.setSpeedByComponentsRange(-0.15f, 0.15f, 0.15f, 0.45f)
.setFadeOut(200, AccelerateInterpolator())
.emitWithGravity(binding.confettiAnchor, Gravity.TOP, 15, 2000)
}
fun configure(type: String, key: String, text: String) {
binding.titleView.text = text
binding.equipButton.visibility = if (type == "gear") View.VISIBLE else View.GONE
when (type) {
"gear" -> {
binding.subtitleView.text = getString(R.string.armoireEquipment_new)
binding.iconView.loadImage("shop_$key")
}
"food" -> {
binding.subtitleView.text = getString(R.string.armoireFood_new)
binding.iconView.loadImage("Pet_Food_$key")
}
else -> {
binding.subtitleView.text = getString(R.string.armoireExp)
binding.iconView.setImageResource(R.drawable.armoire_experience)
}
}
}
}

View file

@ -203,6 +203,9 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
setupBottomnavigationLayoutListener()
viewModel.onCreate()
val args = ArmoireActivityDirections.openArmoireActivity("experience", "", "")
MainNavigationController.navigate(R.id.armoireActivity, args.arguments)
}
override fun setTitle(title: CharSequence?) {

View file

@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.ui.views
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
@ -7,6 +8,7 @@ import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import androidx.core.animation.doOnEnd
import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.isUsingNightModeResources
@ -40,6 +42,7 @@ class CurrencyView : androidx.appcompat.widget.AppCompatTextView {
} catch (_: ArrayIndexOutOfBoundsException) {
!context.isUsingNightModeResources()
}
currency = attributes?.getString(R.styleable.CurrencyView_currency)
visibility = GONE
}
@ -105,13 +108,38 @@ class CurrencyView : androidx.appcompat.widget.AppCompatTextView {
}
}
var minForAbbrevation = 0
var decimals = 2
var animationDuration = 500L
var animationDelay = 0L
private fun update(value: Double) {
text = NumberAbbreviator.abbreviate(context, value, decimals, minForAbbrevation = minForAbbrevation)
}
private fun endUpdate() {
contentDescription = "$text $currencyContentDescription"
updateVisibility()
}
var value = 0.0
set(value) {
if (text.isEmpty() || animationDuration == 0L) {
update(value)
endUpdate()
} else {
val animator = ValueAnimator.ofFloat(field.toFloat(), value.toFloat())
animator.duration = animationDuration
animator.startDelay = animationDelay
animator.addUpdateListener {
update((it.animatedValue as Float).toDouble())
}
animator.doOnEnd {
endUpdate()
}
animator.start()
}
field = value
val abbreviatedValue = NumberAbbreviator.abbreviate(context, value)
text = abbreviatedValue
contentDescription = "$abbreviatedValue $currencyContentDescription"
updateVisibility()
}
var isLocked = false

View file

@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.ui.views
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
@ -164,9 +165,23 @@ class ValueBar(context: Context, attrs: AttributeSet?) : FrameLayout(context, at
binding.descriptionTextView.setTextColor(textColor)
}
var animationDuration = 500L
var animationDelay = 0L
fun set(value: Double, valueMax: Double) {
if (currentValue != value || maxValue != valueMax) {
currentValue = value
if (animationDuration == 0L || binding.valueTextView.text.isEmpty()) {
currentValue = value
} else {
val animator = ValueAnimator.ofInt(currentValue.toInt(), value.toInt())
animator.duration = animationDuration
animator.startDelay = animationDelay
animator.addUpdateListener {
currentValue = (it.animatedValue as Int).toDouble()
updateBar()
}
animator.start()
}
maxValue = valueMax
updateBar()
}

View file

@ -27,6 +27,8 @@ import com.habitrpg.android.habitica.models.shops.Shop
import com.habitrpg.android.habitica.models.shops.ShopItem
import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.ArmoireActivityArgs
import com.habitrpg.android.habitica.ui.activities.ArmoireActivityDirections
import com.habitrpg.android.habitica.ui.views.CurrencyView
import com.habitrpg.android.habitica.ui.views.CurrencyViews
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
@ -358,11 +360,9 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
} else if ("gold" == shopItem.currency && "gem" != shopItem.key) {
observable = inventoryRepository.buyItem(user, shopItem.key, shopItem.value.toDouble(), quantity).map { buyResponse ->
if (shopItem.key == "armoire") {
snackbarText[0] = when {
buyResponse.armoire["type"] == "gear" -> context.getString(R.string.armoireEquipment, buyResponse.armoire["dropText"])
buyResponse.armoire["type"] == "food" -> context.getString(R.string.armoireFood, buyResponse.armoire["dropArticle"] ?: "", buyResponse.armoire["dropText"])
else -> context.getString(R.string.armoireExp)
}
MainNavigationController.navigate(R.id.armoireActivity, ArmoireActivityDirections.openArmoireActivity(buyResponse.armoire["type"] ?: "",
buyResponse.armoire["dropText"] ?: "",
buyResponse.armoire["dropKey"] ?: "").arguments)
}
buyResponse
}

View file

@ -8,7 +8,7 @@ buildscript {
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath 'com.android.tools.build:gradle:7.1.3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath 'com.google.gms:google-services:4.3.10'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'