mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-04-14 11:46:32 +00:00
Merge avatar and equipment screens
This commit is contained in:
parent
8fe9de1223
commit
5ae7f9bbb7
15 changed files with 506 additions and 320 deletions
|
|
@ -83,7 +83,7 @@ GEM
|
||||||
xcpretty-travis-formatter (>= 0.0.3)
|
xcpretty-travis-formatter (>= 0.0.3)
|
||||||
fastlane-plugin-properties (1.1.2)
|
fastlane-plugin-properties (1.1.2)
|
||||||
java-properties
|
java-properties
|
||||||
fastlane-plugin-semantic_release (1.14.1)
|
fastlane-plugin-semantic_release (1.18.0)
|
||||||
fastlane-plugin-versioning_android (0.1.0)
|
fastlane-plugin-versioning_android (0.1.0)
|
||||||
gh_inspector (1.1.3)
|
gh_inspector (1.1.3)
|
||||||
google-api-client (0.38.0)
|
google-api-client (0.38.0)
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ dependencies {
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
androidTestImplementation 'androidx.test:runner:1.4.0'
|
androidTestImplementation 'androidx.test:runner:1.4.0'
|
||||||
androidTestImplementation 'androidx.test:rules:1.4.0'
|
androidTestImplementation 'androidx.test:rules:1.4.0'
|
||||||
debugImplementation 'androidx.fragment:fragment-testing:1.5.2'
|
debugImplementation 'androidx.fragment:fragment-testing:1.5.3'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||||
androidTestImplementation 'androidx.test:core-ktx:1.4.0'
|
androidTestImplementation 'androidx.test:core-ktx:1.4.0'
|
||||||
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
|
||||||
|
|
@ -109,18 +109,18 @@ dependencies {
|
||||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||||
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
|
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
|
||||||
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
|
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
|
||||||
implementation "androidx.fragment:fragment-ktx:1.5.2"
|
implementation "androidx.fragment:fragment-ktx:1.5.3"
|
||||||
implementation "androidx.paging:paging-runtime-ktx:3.1.1"
|
implementation "androidx.paging:paging-runtime-ktx:3.1.1"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
implementation "com.google.android.material:compose-theme-adapter:1.1.18"
|
implementation "com.google.android.material:compose-theme-adapter:1.1.19"
|
||||||
|
|
||||||
implementation 'androidx.activity:activity-compose:1.5.1'
|
implementation 'androidx.activity:activity-compose:1.6.0'
|
||||||
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
|
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
|
||||||
implementation "androidx.compose.material:material:$compose_version"
|
implementation "androidx.compose.material:material:$compose_version"
|
||||||
implementation "androidx.compose.animation:animation:$compose_version"
|
implementation "androidx.compose.animation:animation:$compose_version"
|
||||||
implementation "androidx.compose.ui:ui-tooling:$compose_version"
|
implementation "androidx.compose.ui:ui-tooling:$compose_version"
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1'
|
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
|
||||||
|
|
||||||
implementation 'com.willowtreeapps:signinwithapplebutton:0.3'
|
implementation 'com.willowtreeapps:signinwithapplebutton:0.3'
|
||||||
|
|
||||||
|
|
@ -172,7 +172,7 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
composeOptions {
|
composeOptions {
|
||||||
kotlinCompilerExtensionVersion = "1.3.1"
|
kotlinCompilerExtensionVersion = "1.3.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|
|
||||||
4
Habitica/res/layout/fragment_compose.xml
Normal file
4
Habitica/res/layout/fragment_compose.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.compose.ui.platform.ComposeView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
10
Habitica/res/layout/fragment_compose_scrolling.xml
Normal file
10
Habitica/res/layout/fragment_compose_scrolling.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<androidx.compose.ui.platform.ComposeView
|
||||||
|
android:id="@+id/compose_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
<string name="sidebar_challenges">Challenges</string>
|
<string name="sidebar_challenges">Challenges</string>
|
||||||
<string name="sidebar_section_inventory">Inventory</string>
|
<string name="sidebar_section_inventory">Inventory</string>
|
||||||
<string name="sidebar_avatar">Avatar Customization</string>
|
<string name="sidebar_avatar">Avatar Customization</string>
|
||||||
|
<string name="sidebar_avatar_equipment">Avatar & Equipment</string>
|
||||||
<string name="sidebar_equipment">Equipment</string>
|
<string name="sidebar_equipment">Equipment</string>
|
||||||
<string name="sidebar_stable">Pets & Mounts</string>
|
<string name="sidebar_stable">Pets & Mounts</string>
|
||||||
<string name="sidebar_news">News</string>
|
<string name="sidebar_news">News</string>
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@ class HabiticaFirebaseMessagingService : FirebaseMessagingService() {
|
||||||
PushNotificationManager.displayNotification(remoteMessage, applicationContext)
|
PushNotificationManager.displayNotification(remoteMessage, applicationContext)
|
||||||
|
|
||||||
if (remoteMessage.data["identifier"]?.contains(PushNotificationManager.WON_CHALLENGE_PUSH_NOTIFICATION_KEY) == true) {
|
if (remoteMessage.data["identifier"]?.contains(PushNotificationManager.WON_CHALLENGE_PUSH_NOTIFICATION_KEY) == true) {
|
||||||
if (this::userRepository.isInitialized) {
|
// if (this::userRepository.isInitialized) {
|
||||||
// userRepository.retrieveUser(true).subscribe({}, RxErrorHandler.handleEmptyError())
|
// userRepository.retrieveUser(true).subscribe({}, RxErrorHandler.handleEmptyError())
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ import com.habitrpg.android.habitica.models.user.User
|
||||||
import com.habitrpg.android.habitica.models.user.UserQuestStatus
|
import com.habitrpg.android.habitica.models.user.UserQuestStatus
|
||||||
import com.habitrpg.android.habitica.ui.TutorialView
|
import com.habitrpg.android.habitica.ui.TutorialView
|
||||||
import com.habitrpg.android.habitica.ui.fragments.NavigationDrawerFragment
|
import com.habitrpg.android.habitica.ui.fragments.NavigationDrawerFragment
|
||||||
|
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
|
||||||
import com.habitrpg.android.habitica.ui.viewmodels.MainActivityViewModel
|
import com.habitrpg.android.habitica.ui.viewmodels.MainActivityViewModel
|
||||||
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
|
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
|
||||||
import com.habitrpg.android.habitica.ui.views.AppHeaderView
|
import com.habitrpg.android.habitica.ui.views.AppHeaderView
|
||||||
|
|
@ -212,7 +213,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
|
||||||
setupBottomnavigationLayoutListener()
|
setupBottomnavigationLayoutListener()
|
||||||
|
|
||||||
binding.content.headerView.setContent {
|
binding.content.headerView.setContent {
|
||||||
MdcTheme(setTextColors = true) {
|
HabiticaTheme {
|
||||||
AppHeaderView(viewModel.userViewModel)
|
AppHeaderView(viewModel.userViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,10 @@ import com.habitrpg.android.habitica.databinding.DrawerMainBinding
|
||||||
import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds
|
import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds
|
||||||
import com.habitrpg.android.habitica.extensions.getRemainingString
|
import com.habitrpg.android.habitica.extensions.getRemainingString
|
||||||
import com.habitrpg.android.habitica.extensions.getShortRemainingString
|
import com.habitrpg.android.habitica.extensions.getShortRemainingString
|
||||||
import com.habitrpg.common.habitica.extensions.getThemeColor
|
|
||||||
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
|
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
|
||||||
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||||
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
|
||||||
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
||||||
|
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
||||||
import com.habitrpg.android.habitica.models.WorldStateEvent
|
import com.habitrpg.android.habitica.models.WorldStateEvent
|
||||||
import com.habitrpg.android.habitica.models.inventory.Item
|
import com.habitrpg.android.habitica.models.inventory.Item
|
||||||
import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion
|
import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion
|
||||||
|
|
@ -368,10 +367,9 @@ class NavigationDrawerFragment : DialogFragment() {
|
||||||
items.add(HabiticaDrawerItem(R.id.timeTravelersShopFragment, SIDEBAR_SHOPS_TIMETRAVEL, context.getString(R.string.timeTravelers)))
|
items.add(HabiticaDrawerItem(R.id.timeTravelersShopFragment, SIDEBAR_SHOPS_TIMETRAVEL, context.getString(R.string.timeTravelers)))
|
||||||
|
|
||||||
items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_section_inventory), true))
|
items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_section_inventory), true))
|
||||||
|
items.add(HabiticaDrawerItem(R.id.avatarOverviewFragment, SIDEBAR_AVATAR, context.getString(R.string.sidebar_avatar_equipment)))
|
||||||
items.add(HabiticaDrawerItem(R.id.itemsFragment, SIDEBAR_ITEMS, context.getString(R.string.sidebar_items)))
|
items.add(HabiticaDrawerItem(R.id.itemsFragment, SIDEBAR_ITEMS, context.getString(R.string.sidebar_items)))
|
||||||
items.add(HabiticaDrawerItem(R.id.equipmentOverviewFragment, SIDEBAR_EQUIPMENT, context.getString(R.string.sidebar_equipment)))
|
|
||||||
items.add(HabiticaDrawerItem(R.id.stableFragment, SIDEBAR_STABLE, context.getString(R.string.sidebar_stable)))
|
items.add(HabiticaDrawerItem(R.id.stableFragment, SIDEBAR_STABLE, context.getString(R.string.sidebar_stable)))
|
||||||
items.add(HabiticaDrawerItem(R.id.avatarOverviewFragment, SIDEBAR_AVATAR, context.getString(R.string.sidebar_avatar)))
|
|
||||||
items.add(HabiticaDrawerItem(R.id.gemPurchaseActivity, SIDEBAR_GEMS, context.getString(R.string.sidebar_gems)))
|
items.add(HabiticaDrawerItem(R.id.gemPurchaseActivity, SIDEBAR_GEMS, context.getString(R.string.sidebar_gems)))
|
||||||
items.add(HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_SUBSCRIPTION, context.getString(R.string.sidebar_subscription)))
|
items.add(HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_SUBSCRIPTION, context.getString(R.string.sidebar_subscription)))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
package com.habitrpg.android.habitica.ui.fragments.inventory.customization
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
|
||||||
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.databinding.FragmentRefreshRecyclerviewBinding
|
|
||||||
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
|
||||||
import com.habitrpg.android.habitica.models.responses.UnlockResponse
|
|
||||||
import com.habitrpg.android.habitica.models.user.User
|
|
||||||
import com.habitrpg.android.habitica.ui.adapter.CustomizationEquipmentRecyclerViewAdapter
|
|
||||||
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 kotlinx.coroutines.launch
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class AvatarEquipmentFragment :
|
|
||||||
BaseMainFragment<FragmentRefreshRecyclerviewBinding>(),
|
|
||||||
SwipeRefreshLayout.OnRefreshListener {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var inventoryRepository: InventoryRepository
|
|
||||||
@Inject
|
|
||||||
lateinit var userViewModel: MainUserViewModel
|
|
||||||
|
|
||||||
override var binding: FragmentRefreshRecyclerviewBinding? = null
|
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRefreshRecyclerviewBinding {
|
|
||||||
return FragmentRefreshRecyclerviewBinding.inflate(inflater, container, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
var type: String? = null
|
|
||||||
var category: String? = null
|
|
||||||
private var activeEquipment: String? = null
|
|
||||||
|
|
||||||
internal var adapter: CustomizationEquipmentRecyclerViewAdapter = CustomizationEquipmentRecyclerViewAdapter()
|
|
||||||
internal var layoutManager: GridLayoutManager = GridLayoutManager(activity, 2)
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View? {
|
|
||||||
showsBackButton = true
|
|
||||||
compositeSubscription.add(
|
|
||||||
adapter.getSelectCustomizationEvents()
|
|
||||||
.flatMap { equipment ->
|
|
||||||
val key = (if (equipment.key?.isNotBlank() != true) activeEquipment else equipment.key) ?: ""
|
|
||||||
inventoryRepository.equip(if (userViewModel.user.value?.preferences?.costume == true) "costume" else "equipped", key)
|
|
||||||
}
|
|
||||||
.subscribe({ }, ExceptionHandler.rx())
|
|
||||||
)
|
|
||||||
compositeSubscription.add(
|
|
||||||
adapter.getUnlockCustomizationEvents()
|
|
||||||
.flatMap<UnlockResponse> {
|
|
||||||
Flowable.empty()
|
|
||||||
}
|
|
||||||
.subscribe({ }, ExceptionHandler.rx())
|
|
||||||
)
|
|
||||||
return super.onCreateView(inflater, container, savedInstanceState)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
showsBackButton = true
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
arguments?.let {
|
|
||||||
val args = AvatarEquipmentFragmentArgs.fromBundle(it)
|
|
||||||
type = args.type
|
|
||||||
if (args.category.isNotEmpty()) {
|
|
||||||
category = args.category
|
|
||||||
}
|
|
||||||
}
|
|
||||||
binding?.refreshLayout?.setOnRefreshListener(this)
|
|
||||||
setGridSpanCount(view.width)
|
|
||||||
val layoutManager = GridLayoutManager(activity, 4)
|
|
||||||
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
|
|
||||||
override fun getSpanSize(position: Int): Int {
|
|
||||||
return if (adapter.getItemViewType(position) == 0) {
|
|
||||||
layoutManager.spanCount
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
binding?.recyclerView?.layoutManager = layoutManager
|
|
||||||
binding?.recyclerView?.addItemDecoration(MarginDecoration(context))
|
|
||||||
|
|
||||||
binding?.recyclerView?.adapter = adapter
|
|
||||||
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
|
|
||||||
this.loadEquipment()
|
|
||||||
|
|
||||||
userViewModel.user.observe(viewLifecycleOwner) { updateUser(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun injectFragment(component: UserComponent) {
|
|
||||||
component.inject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadEquipment() {
|
|
||||||
val type = this.type ?: return
|
|
||||||
compositeSubscription.add(
|
|
||||||
inventoryRepository.getEquipmentType(type, category ?: "").subscribe(
|
|
||||||
{
|
|
||||||
adapter.setEquipment(it)
|
|
||||||
},
|
|
||||||
ExceptionHandler.rx()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setGridSpanCount(width: Int) {
|
|
||||||
val itemWidth = context?.resources?.getDimension(R.dimen.customization_width) ?: 0F
|
|
||||||
var spanCount = (width / itemWidth).toInt()
|
|
||||||
if (spanCount == 0) {
|
|
||||||
spanCount = 1
|
|
||||||
}
|
|
||||||
layoutManager.spanCount = spanCount
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateUser(user: User?) {
|
|
||||||
this.updateActiveCustomization(user)
|
|
||||||
this.adapter.gemBalance = user?.gemCount ?: 0
|
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateActiveCustomization(user: User?) {
|
|
||||||
if (this.type == null || user?.preferences == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
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
|
|
||||||
"eyewear" -> outfit?.eyeWear
|
|
||||||
else -> ""
|
|
||||||
}
|
|
||||||
if (activeEquipment != null) {
|
|
||||||
this.activeEquipment = activeEquipment
|
|
||||||
this.adapter.activeEquipment = activeEquipment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRefresh() {
|
|
||||||
lifecycleScope.launch(ExceptionHandler.coroutine()) {
|
|
||||||
userRepository.retrieveUser(true, true)
|
|
||||||
binding?.refreshLayout?.isRefreshing = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,46 +5,68 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.Switch
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.habitrpg.android.habitica.R
|
||||||
import com.habitrpg.android.habitica.components.UserComponent
|
import com.habitrpg.android.habitica.components.UserComponent
|
||||||
import com.habitrpg.android.habitica.databinding.FragmentAvatarOverviewBinding
|
import com.habitrpg.android.habitica.databinding.FragmentComposeScrollingBinding
|
||||||
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
|
||||||
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
||||||
import com.habitrpg.android.habitica.models.user.User
|
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
||||||
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
|
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
|
||||||
|
import com.habitrpg.android.habitica.ui.fragments.inventory.equipment.EquipmentOverviewFragmentDirections
|
||||||
|
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
|
||||||
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
|
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
|
||||||
|
import com.habitrpg.android.habitica.ui.views.AvatarCustomizationOverviewView
|
||||||
|
import com.habitrpg.android.habitica.ui.views.EquipmentOverviewView
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AvatarOverviewFragment : BaseMainFragment<FragmentAvatarOverviewBinding>(), AdapterView.OnItemSelectedListener {
|
class AvatarOverviewFragment : BaseMainFragment<FragmentComposeScrollingBinding>(),
|
||||||
|
AdapterView.OnItemSelectedListener {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var userViewModel: MainUserViewModel
|
lateinit var userViewModel: MainUserViewModel
|
||||||
|
|
||||||
override var binding: FragmentAvatarOverviewBinding? = null
|
override var binding: FragmentComposeScrollingBinding? = null
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentAvatarOverviewBinding {
|
override fun createBinding(
|
||||||
return FragmentAvatarOverviewBinding.inflate(inflater, container, false)
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?
|
||||||
|
): FragmentComposeScrollingBinding {
|
||||||
|
return FragmentComposeScrollingBinding.inflate(inflater, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onCreateView(
|
||||||
super.onViewCreated(view, savedInstanceState)
|
inflater: LayoutInflater,
|
||||||
binding?.avatarSizeSpinner?.onItemSelectedListener = this
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
binding?.avatarShirtView?.setOnClickListener { displayCustomizationFragment("shirt", null) }
|
): View? {
|
||||||
binding?.avatarSkinView?.setOnClickListener { displayCustomizationFragment("skin", null) }
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
binding?.avatarChairView?.setOnClickListener { displayCustomizationFragment("chair", null) }
|
binding?.composeView?.apply {
|
||||||
binding?.avatarGlassesView?.setOnClickListener { displayEquipmentFragment("eyewear", "glasses") }
|
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||||
binding?.avatarAnimalEarsView?.setOnClickListener { displayEquipmentFragment("headAccessory", "animal") }
|
setContent {
|
||||||
binding?.avatarAnimalTailView?.setOnClickListener { displayEquipmentFragment("back", "animal") }
|
HabiticaTheme {
|
||||||
binding?.avatarHeadbandView?.setOnClickListener { displayEquipmentFragment("headAccessory", "headband") }
|
AvatarOverviewView(userViewModel, { type, category ->
|
||||||
binding?.avatarHairColorView?.setOnClickListener { displayCustomizationFragment("hair", "color") }
|
displayCustomizationFragment(type, category)
|
||||||
binding?.avatarHairBangsView?.setOnClickListener { displayCustomizationFragment("hair", "bangs") }
|
}, { type, equipped, isCostume ->
|
||||||
binding?.avatarHairBaseView?.setOnClickListener { displayCustomizationFragment("hair", "base") }
|
displayEquipmentFragment(type, equipped, isCostume)
|
||||||
binding?.avatarAccentView?.setOnClickListener { displayCustomizationFragment("hair", "flower") }
|
})
|
||||||
binding?.avatarHairBeardView?.setOnClickListener { displayCustomizationFragment("hair", "beard") }
|
}
|
||||||
binding?.avatarHairMustacheView?.setOnClickListener { displayCustomizationFragment("hair", "mustache") }
|
}
|
||||||
binding?.avatarBackgroundView?.setOnClickListener { displayCustomizationFragment("background", null) }
|
}
|
||||||
|
return view
|
||||||
userViewModel.user.observe(viewLifecycleOwner) { updateUser(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun injectFragment(component: UserComponent) {
|
override fun injectFragment(component: UserComponent) {
|
||||||
|
|
@ -52,46 +74,16 @@ class AvatarOverviewFragment : BaseMainFragment<FragmentAvatarOverviewBinding>()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun displayCustomizationFragment(type: String, category: String?) {
|
private fun displayCustomizationFragment(type: String, category: String?) {
|
||||||
MainNavigationController.navigate(AvatarOverviewFragmentDirections.openAvatarDetail(type, category ?: ""))
|
MainNavigationController.navigate(
|
||||||
|
AvatarOverviewFragmentDirections.openAvatarDetail(
|
||||||
|
type,
|
||||||
|
category ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun displayEquipmentFragment(type: String, category: String?) {
|
private fun displayEquipmentFragment(type: String, equipped: String?, isCostume: Boolean = false) {
|
||||||
MainNavigationController.navigate(AvatarOverviewFragmentDirections.openAvatarEquipment(type, category ?: ""))
|
MainNavigationController.navigate(EquipmentOverviewFragmentDirections.openEquipmentDetail(type, isCostume, equipped ?: ""))
|
||||||
}
|
|
||||||
|
|
||||||
fun updateUser(user: User?) {
|
|
||||||
this.setSize(user?.preferences?.size)
|
|
||||||
setCustomizations(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setCustomizations(user: User?) {
|
|
||||||
if (user == null) return
|
|
||||||
binding?.avatarShirtView?.customizationIdentifier = user.preferences?.size + "_shirt_" + user.preferences?.shirt
|
|
||||||
binding?.avatarSkinView?.customizationIdentifier = "skin_" + user.preferences?.skin
|
|
||||||
val chair = user.preferences?.chair
|
|
||||||
binding?.avatarChairView?.customizationIdentifier = if (chair?.startsWith("handleless") == true) "chair_$chair" else chair
|
|
||||||
binding?.avatarGlassesView?.equipmentIdentifier = user.equipped?.eyeWear
|
|
||||||
binding?.avatarAnimalEarsView?.equipmentIdentifier = user.equipped?.headAccessory
|
|
||||||
binding?.avatarHeadbandView?.equipmentIdentifier = user.equipped?.headAccessory
|
|
||||||
binding?.avatarAnimalTailView?.equipmentIdentifier = user.equipped?.back
|
|
||||||
binding?.avatarHairColorView?.customizationIdentifier = if (user.preferences?.hair?.color != null && user.preferences?.hair?.color != "") "hair_bangs_1_" + user.preferences?.hair?.color else ""
|
|
||||||
binding?.avatarHairBangsView?.customizationIdentifier = if (user.preferences?.hair?.bangs != null && user.preferences?.hair?.bangs != 0) "hair_bangs_" + user.preferences?.hair?.bangs + "_" + user.preferences?.hair?.color else ""
|
|
||||||
binding?.avatarHairBaseView?.customizationIdentifier = if (user.preferences?.hair?.base != null && user.preferences?.hair?.base != 0) "hair_base_" + user.preferences?.hair?.base + "_" + user.preferences?.hair?.color else ""
|
|
||||||
binding?.avatarAccentView?.customizationIdentifier = if (user.preferences?.hair?.flower != null && user.preferences?.hair?.flower != 0) "hair_flower_" + user.preferences?.hair?.flower else ""
|
|
||||||
binding?.avatarHairBeardView?.customizationIdentifier = if (user.preferences?.hair?.beard != null && user.preferences?.hair?.beard != 0) "hair_beard_" + user.preferences?.hair?.beard + "_" + user.preferences?.hair?.color else ""
|
|
||||||
binding?.avatarHairMustacheView?.customizationIdentifier = if (user.preferences?.hair?.mustache != null && user.preferences?.hair?.mustache != 0) "hair_mustache_" + user.preferences?.hair?.mustache + "_" + user.preferences?.hair?.color else ""
|
|
||||||
binding?.avatarBackgroundView?.customizationIdentifier = "background_" + user.preferences?.background
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setSize(size: String?) {
|
|
||||||
if (size == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (size == "slim") {
|
|
||||||
binding?.avatarSizeSpinner?.setSelection(0, false)
|
|
||||||
} else {
|
|
||||||
binding?.avatarSizeSpinner?.setSelection(1, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
|
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
|
||||||
|
|
@ -103,5 +95,68 @@ class AvatarOverviewFragment : BaseMainFragment<FragmentAvatarOverviewBinding>()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNothingSelected(parent: AdapterView<*>) { /* no-on */ }
|
override fun onNothingSelected(parent: AdapterView<*>) { /* no-on */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AvatarOverviewView(userViewModel: MainUserViewModel,
|
||||||
|
onCustomizationTap: (String, String?) -> Unit,
|
||||||
|
onEquipmentTap: (String, String?, Boolean) -> Unit
|
||||||
|
) {
|
||||||
|
val user by userViewModel.user.observeAsState()
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.padding(bottom = 16.dp)) {
|
||||||
|
Row(Modifier.padding(horizontal = 12.dp, vertical = 15.dp)) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.avatar_size),
|
||||||
|
style = HabiticaTheme.typography.subtitle2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
AvatarCustomizationOverviewView(user?.preferences, onCustomizationTap)
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.padding(horizontal = 12.dp)
|
||||||
|
.padding(top = 15.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(stringResource(R.string.equipped), style = HabiticaTheme.typography.subtitle2)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.equip_automatically),
|
||||||
|
style = HabiticaTheme.typography.body2
|
||||||
|
)
|
||||||
|
Switch(checked = user?.preferences?.autoEquip == true, onCheckedChange = {
|
||||||
|
userViewModel.updateUser("preferences.autoEquip", it)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
EquipmentOverviewView(user?.items?.gear?.equipped, { type, equipped ->
|
||||||
|
onEquipmentTap(type, equipped, false)
|
||||||
|
})
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.padding(horizontal = 12.dp)
|
||||||
|
.padding(top = 15.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.costume),
|
||||||
|
style = HabiticaTheme.typography.subtitle2
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.wear_costume),
|
||||||
|
style = HabiticaTheme.typography.body2
|
||||||
|
)
|
||||||
|
Switch(checked = user?.preferences?.costume == true, onCheckedChange = {
|
||||||
|
userViewModel.updateUser("preferences.costume", it)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
AnimatedVisibility(visible = user?.preferences?.costume == true) {
|
||||||
|
EquipmentOverviewView(user?.items?.gear?.costume, { type, equipped ->
|
||||||
|
onEquipmentTap(type, equipped, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
package com.habitrpg.android.habitica.ui.fragments.inventory.equipment
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import com.habitrpg.android.habitica.components.UserComponent
|
|
||||||
import com.habitrpg.android.habitica.databinding.FragmentEquipmentOverviewBinding
|
|
||||||
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
|
||||||
import com.habitrpg.android.habitica.models.user.Gear
|
|
||||||
import com.habitrpg.android.habitica.models.user.Outfit
|
|
||||||
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
|
|
||||||
import com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment.EquipmentOverviewViewModel
|
|
||||||
import com.habitrpg.android.habitica.ui.views.equipment.EquipmentOverviewView
|
|
||||||
|
|
||||||
class EquipmentOverviewFragment : BaseMainFragment<FragmentEquipmentOverviewBinding>() {
|
|
||||||
|
|
||||||
private val viewModel: EquipmentOverviewViewModel by viewModels()
|
|
||||||
|
|
||||||
override var binding: FragmentEquipmentOverviewBinding? = null
|
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentEquipmentOverviewBinding {
|
|
||||||
return FragmentEquipmentOverviewBinding.inflate(inflater, container, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
binding?.battlegearView?.onNavigate = { type, equipped ->
|
|
||||||
displayEquipmentDetailList(type, equipped, false)
|
|
||||||
}
|
|
||||||
binding?.costumeView?.onNavigate = { type, equipped ->
|
|
||||||
displayEquipmentDetailList(type, equipped, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding?.autoEquipSwitch?.setOnCheckedChangeListener { _, isChecked ->
|
|
||||||
if (isChecked == viewModel.user.value?.preferences?.autoEquip) return@setOnCheckedChangeListener
|
|
||||||
viewModel.updateUser("preferences.autoEquip", isChecked)
|
|
||||||
}
|
|
||||||
binding?.costumeSwitch?.setOnCheckedChangeListener { _, isChecked ->
|
|
||||||
if (isChecked == viewModel.user.value?.preferences?.costume) return@setOnCheckedChangeListener
|
|
||||||
viewModel.updateUser("preferences.costume", isChecked)
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.user.observe(viewLifecycleOwner) {
|
|
||||||
it?.items?.gear?.let {
|
|
||||||
updateGearData(it)
|
|
||||||
}
|
|
||||||
binding?.autoEquipSwitch?.isChecked = viewModel.usesAutoEquip
|
|
||||||
binding?.costumeSwitch?.isChecked = viewModel.usesCostume
|
|
||||||
|
|
||||||
binding?.costumeView?.isEnabled = viewModel.usesCostume
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun injectFragment(component: UserComponent) {
|
|
||||||
component.inject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun displayEquipmentDetailList(type: String, equipped: String?, isCostume: Boolean?) {
|
|
||||||
MainNavigationController.navigate(EquipmentOverviewFragmentDirections.openEquipmentDetail(type, isCostume ?: false, equipped ?: ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateGearData(gear: Gear) {
|
|
||||||
updateOutfit(binding?.battlegearView, gear.equipped)
|
|
||||||
updateOutfit(binding?.costumeView, gear.costume)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateOutfit(view: EquipmentOverviewView?, outfit: Outfit?) {
|
|
||||||
if (outfit?.weapon?.isNotEmpty() == true) {
|
|
||||||
viewModel.getGear(outfit.weapon) {
|
|
||||||
if (it.isValid) {
|
|
||||||
view?.updateData(outfit, it.twoHanded)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
view?.updateData(outfit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
package com.habitrpg.android.habitica.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Typography
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.google.android.material.composethemeadapter.createMdcTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HabiticaTheme(
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
|
val (colors, typography, shapes) = createMdcTheme(
|
||||||
|
context = context,
|
||||||
|
layoutDirection = layoutDirection,
|
||||||
|
setTextColors = true
|
||||||
|
)
|
||||||
|
MaterialTheme(
|
||||||
|
colors = colors ?: MaterialTheme.colors,
|
||||||
|
typography = Typography(
|
||||||
|
defaultFontFamily = FontFamily.Default,
|
||||||
|
h1 = TextStyle(
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 20.sp,
|
||||||
|
letterSpacing = (0.05).sp
|
||||||
|
),
|
||||||
|
h2 = TextStyle(
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 28.sp,
|
||||||
|
letterSpacing = (0.05).sp
|
||||||
|
),
|
||||||
|
subtitle1 = TextStyle(
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
),
|
||||||
|
subtitle2 = TextStyle(
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
letterSpacing = 0.1.sp
|
||||||
|
),
|
||||||
|
body1 = TextStyle(
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
letterSpacing = 0.35.sp,
|
||||||
|
lineHeight = 16.sp
|
||||||
|
),
|
||||||
|
body2 = TextStyle(
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
letterSpacing = 0.2.sp,
|
||||||
|
lineHeight = 16.sp
|
||||||
|
),
|
||||||
|
button = TextStyle(
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
letterSpacing = 1.25.sp
|
||||||
|
),
|
||||||
|
caption = TextStyle(
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 12.sp
|
||||||
|
),
|
||||||
|
overline = TextStyle(
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 10.sp,
|
||||||
|
letterSpacing = 1.5.sp
|
||||||
|
)
|
||||||
|
),
|
||||||
|
shapes = shapes ?: MaterialTheme.shapes,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val Typography.caption1
|
||||||
|
get() = caption
|
||||||
|
val Typography.caption2
|
||||||
|
get() = TextStyle(
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
letterSpacing = 0.4.sp
|
||||||
|
)
|
||||||
|
val Typography.caption3
|
||||||
|
get() = TextStyle(
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
letterSpacing = 0.3.sp,
|
||||||
|
lineHeight = 14.sp
|
||||||
|
)
|
||||||
|
val Typography.caption4
|
||||||
|
get() = TextStyle(
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
letterSpacing = 0.35.sp
|
||||||
|
)
|
||||||
|
val Typography.subtitle3
|
||||||
|
get() = TextStyle(
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
letterSpacing = 0.15.sp
|
||||||
|
)
|
||||||
|
object HabiticaTheme {
|
||||||
|
val typography: Typography
|
||||||
|
@Composable
|
||||||
|
get() = MaterialTheme.typography
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,217 @@
|
||||||
|
package com.habitrpg.android.habitica.ui.views
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.res.colorResource
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.habitrpg.android.habitica.R
|
||||||
|
import com.habitrpg.android.habitica.models.user.Outfit
|
||||||
|
import com.habitrpg.android.habitica.models.user.Preferences
|
||||||
|
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
|
||||||
|
import com.habitrpg.android.habitica.ui.theme.caption2
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OverviewItem(
|
||||||
|
text: String,
|
||||||
|
iconName: String?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
isTwoHanded: Boolean = false
|
||||||
|
) {
|
||||||
|
val hasIcon = iconName?.isNotBlank() == true
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier
|
||||||
|
.width(70.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.size(70.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.background(colorResource(if (hasIcon) R.color.gray_700 else R.color.gray_10)),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
if (isTwoHanded) {
|
||||||
|
Image(painterResource(R.drawable.equipment_two_handed), null)
|
||||||
|
} else if (hasIcon) {
|
||||||
|
PixelArtView(
|
||||||
|
imageName = iconName, Modifier
|
||||||
|
.size(70.dp)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Image(painterResource(R.drawable.equipment_nothing_equipped), null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
style = HabiticaTheme.typography.caption2,
|
||||||
|
color = colorResource(R.color.gray_400),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(top = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EquipmentOverviewView(
|
||||||
|
outfit: Outfit?,
|
||||||
|
onEquipmentTap: (String, String?) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(18.dp),
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(6.dp))
|
||||||
|
.background(colorResource(R.color.gray_50))
|
||||||
|
.padding(12.dp)
|
||||||
|
) {
|
||||||
|
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
|
||||||
|
OverviewItem(stringResource(R.string.outfit_weapon), outfit?.weapon.let { "shop_$it" }, Modifier.clickable {
|
||||||
|
onEquipmentTap("weapon", null)
|
||||||
|
})
|
||||||
|
OverviewItem(stringResource(R.string.outfit_shield), outfit?.shield.let { "shop_$it" }, Modifier.clickable {
|
||||||
|
onEquipmentTap("shield", null)
|
||||||
|
})
|
||||||
|
OverviewItem(stringResource(R.string.outfit_head), outfit?.head.let { "shop_$it" }, Modifier.clickable {
|
||||||
|
onEquipmentTap("head", null)
|
||||||
|
})
|
||||||
|
OverviewItem(stringResource(R.string.outfit_armor), outfit?.armor.let { "shop_$it" }, Modifier.clickable {
|
||||||
|
onEquipmentTap("armor", null)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
|
||||||
|
OverviewItem(
|
||||||
|
stringResource(R.string.outfit_headAccessory),
|
||||||
|
outfit?.headAccessory.let { "shop_$it" }, Modifier.clickable {
|
||||||
|
onEquipmentTap("headAccessory", null)
|
||||||
|
})
|
||||||
|
OverviewItem(stringResource(R.string.outfit_body), outfit?.body.let { "shop_$it" }, Modifier.clickable {
|
||||||
|
onEquipmentTap("body", null)
|
||||||
|
})
|
||||||
|
OverviewItem(stringResource(R.string.outfit_back), outfit?.back.let { "shop_$it" }, Modifier.clickable {
|
||||||
|
onEquipmentTap("back", null)
|
||||||
|
})
|
||||||
|
OverviewItem(
|
||||||
|
stringResource(R.string.outfit_eyewear),
|
||||||
|
outfit?.eyeWear.let { "shop_$it" }, Modifier.clickable {
|
||||||
|
onEquipmentTap("eyewear", null)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AvatarCustomizationOverviewView(
|
||||||
|
preferences: Preferences?,
|
||||||
|
onCustomizationTap: (String, String?) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(18.dp),
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(6.dp))
|
||||||
|
.background(colorResource(R.color.gray_50))
|
||||||
|
.padding(12.dp)
|
||||||
|
) {
|
||||||
|
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
|
||||||
|
OverviewItem(
|
||||||
|
stringResource(R.string.avatar_shirt),
|
||||||
|
preferences?.shirt.let { "${preferences?.size}_shirt$it" }, Modifier.clickable {
|
||||||
|
onCustomizationTap("shirt", null)
|
||||||
|
})
|
||||||
|
OverviewItem(
|
||||||
|
stringResource(R.string.avatar_skin),
|
||||||
|
preferences?.skin.let { "skin_$it" },
|
||||||
|
Modifier.clickable {
|
||||||
|
onCustomizationTap("skin", null)
|
||||||
|
})
|
||||||
|
OverviewItem(
|
||||||
|
stringResource(R.string.avatar_hair_color),
|
||||||
|
if (preferences?.hair?.color != null && preferences.hair?.color != "") "hair_bangs_1_" + preferences.hair?.color else "",
|
||||||
|
Modifier.clickable {
|
||||||
|
onCustomizationTap("hair", "color")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
OverviewItem(
|
||||||
|
stringResource(R.string.avatar_hair_bangs),
|
||||||
|
if (preferences?.hair?.bangs != null && preferences.hair?.bangs != 0) "hair_bangs_" + preferences.hair?.bangs + "_" + preferences.hair?.color else "",
|
||||||
|
Modifier.clickable {
|
||||||
|
onCustomizationTap("hair", "bangs")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
|
||||||
|
OverviewItem(
|
||||||
|
stringResource(R.string.avatar_style),
|
||||||
|
if (preferences?.hair?.base != null && preferences.hair?.base != 0) "hair_base_" + preferences.hair?.base + "_" + preferences.hair?.color else "",
|
||||||
|
Modifier.clickable {
|
||||||
|
onCustomizationTap("hair", "base")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
OverviewItem(
|
||||||
|
stringResource(R.string.avatar_mustache),
|
||||||
|
if (preferences?.hair?.mustache != null && preferences.hair?.mustache != 0) "hair_mustache_" + preferences.hair?.mustache + "_" + preferences.hair?.color else "",
|
||||||
|
Modifier.clickable {
|
||||||
|
onCustomizationTap("hair", "mustache")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
OverviewItem(
|
||||||
|
stringResource(R.string.avatar_beard),
|
||||||
|
if (preferences?.hair?.beard != null && preferences.hair?.beard != 0) "hair_beard_" + preferences.hair?.beard + "_" + preferences.hair?.color else "",
|
||||||
|
Modifier.clickable {
|
||||||
|
onCustomizationTap("hair", "beard")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
OverviewItem(
|
||||||
|
stringResource(R.string.avatar_flower),
|
||||||
|
if (preferences?.hair?.flower != null && preferences.hair?.flower != 0) "hair_flower_" + preferences.hair?.flower else "",
|
||||||
|
Modifier.clickable {
|
||||||
|
onCustomizationTap("hair", "flower")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
|
||||||
|
OverviewItem(
|
||||||
|
stringResource(R.string.avatar_wheelchair),
|
||||||
|
preferences?.chair?.let { if (it.startsWith("handleless")) "chair_$it" else it })
|
||||||
|
OverviewItem(
|
||||||
|
stringResource(R.string.avatar_background),
|
||||||
|
preferences?.background.let { "background_$it" })
|
||||||
|
Box(Modifier.size(70.dp))
|
||||||
|
Box(Modifier.size(70.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun EquipmentOverviewItemPreview() {
|
||||||
|
Column(Modifier.width(320.dp)) {
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OverviewItem("Main-Hand", "shop_weapon_warrior_1")
|
||||||
|
OverviewItem("Off-Hand", null, isTwoHanded = true)
|
||||||
|
OverviewItem("Armor", null)
|
||||||
|
}
|
||||||
|
EquipmentOverviewView(null, { _, _ -> })
|
||||||
|
AvatarCustomizationOverviewView(null, { _, _ -> })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.habitrpg.android.habitica.ui.views
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import com.habitrpg.common.habitica.extensions.loadImage
|
||||||
|
import com.habitrpg.common.habitica.views.PixelArtView
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PixelArtView(
|
||||||
|
imageName: String?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
imageFormat: String? = null
|
||||||
|
) {
|
||||||
|
AndroidView(
|
||||||
|
modifier = modifier, // Occupy the max size in the Compose UI tree
|
||||||
|
factory = { context ->
|
||||||
|
PixelArtView(context)
|
||||||
|
},
|
||||||
|
update = { view ->
|
||||||
|
if (imageName != null) {
|
||||||
|
view.loadImage(imageName, imageFormat)
|
||||||
|
} else {
|
||||||
|
view.bitmap = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ buildscript {
|
||||||
coroutines_version = '1.6.4'
|
coroutines_version = '1.6.4'
|
||||||
daggerhilt_version = '2.42'
|
daggerhilt_version = '2.42'
|
||||||
firebase_bom = '30.2.0'
|
firebase_bom = '30.2.0'
|
||||||
kotlin_version = '1.7.10'
|
kotlin_version = '1.7.20'
|
||||||
lifecycle_version = '2.5.1'
|
lifecycle_version = '2.5.1'
|
||||||
markwon_version = '4.6.2'
|
markwon_version = '4.6.2'
|
||||||
moshi_version = '1.13.0'
|
moshi_version = '1.13.0'
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue