Skip to content

Commit fccd8ca

Browse files
committed
fix wear os layout issue
1 parent f1a3ce9 commit fccd8ca

File tree

10 files changed

+412
-66
lines changed

10 files changed

+412
-66
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,4 @@ fastlane/metadata/android/*/changelogs/
7272
output-metadata.json
7373
/Habitica/jacoco.exec
7474
*.dm
75+
/fastlane/upload_slack.py

Habitica/res/navigation/navigation.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@
114114
<action
115115
android:id="@+id/openEquipmentDetail"
116116
app:destination="@id/equipmentDetailFragment" />
117+
<action
118+
android:id="@+id/openComposeAvatarEquipment"
119+
app:destination="@id/composeAvatarEquipmentFragment" />
117120
<action
118121
android:id="@+id/openAvatarEquipment"
119122
app:destination="@id/avatarEquipmentFragment" />
@@ -129,6 +132,17 @@
129132
android:name="category"
130133
app:argType="string" />
131134
</fragment>
135+
<fragment
136+
android:id="@+id/composeAvatarEquipmentFragment"
137+
android:name="com.habitrpg.android.habitica.ui.fragments.inventory.customization.ComposeAvatarEquipmentFragment"
138+
android:label="@string/sidebar_avatar" >
139+
<argument
140+
android:name="type"
141+
app:argType="string" />
142+
<argument
143+
android:name="category"
144+
app:argType="string" />
145+
</fragment>
132146
<fragment
133147
android:id="@+id/equipmentOverviewFragment"
134148
android:name="com.habitrpg.android.habitica.ui.fragments.inventory.customization.EquipmentOverviewFragment"

Habitica/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,7 @@
15141514
<string name="spring">Spring</string>
15151515
<string name="summer">Summer</string>
15161516
<string name="fall">Fall</string>
1517+
<string name="animal_tails">Animal Tails</string>
15171518

15181519
<plurals name="you_x_others">
15191520
<item quantity="zero">You</item>

Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,11 @@ open class AvatarOverviewFragment :
148148
type: String,
149149
category: String?,
150150
) {
151-
MainNavigationController.navigate(AvatarOverviewFragmentDirections.openAvatarEquipment(type, category ?: ""))
151+
if (appConfigManager.enableCustomizationShop()) {
152+
MainNavigationController.navigate(AvatarOverviewFragmentDirections.openComposeAvatarEquipment(type, category ?: ""))
153+
} else {
154+
MainNavigationController.navigate(AvatarOverviewFragmentDirections.openAvatarEquipment(type, category ?: ""))
155+
}
152156
}
153157

154158
private fun displayEquipmentFragment(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
package com.habitrpg.android.habitica.ui.fragments.inventory.customization
2+
3+
import android.os.Bundle
4+
import android.view.LayoutInflater
5+
import android.view.View
6+
import android.view.ViewGroup
7+
import androidx.compose.foundation.Image
8+
import androidx.compose.foundation.background
9+
import androidx.compose.foundation.border
10+
import androidx.compose.foundation.clickable
11+
import androidx.compose.foundation.layout.Arrangement
12+
import androidx.compose.foundation.layout.Box
13+
import androidx.compose.foundation.layout.Column
14+
import androidx.compose.foundation.layout.PaddingValues
15+
import androidx.compose.foundation.layout.fillMaxWidth
16+
import androidx.compose.foundation.layout.height
17+
import androidx.compose.foundation.layout.padding
18+
import androidx.compose.foundation.layout.size
19+
import androidx.compose.foundation.lazy.grid.GridCells
20+
import androidx.compose.foundation.lazy.grid.GridItemSpan
21+
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
22+
import androidx.compose.foundation.lazy.grid.items
23+
import androidx.compose.foundation.shape.RoundedCornerShape
24+
import androidx.compose.material3.Text
25+
import androidx.compose.runtime.Composable
26+
import androidx.compose.runtime.getValue
27+
import androidx.compose.runtime.livedata.observeAsState
28+
import androidx.compose.runtime.mutableStateListOf
29+
import androidx.compose.runtime.mutableStateOf
30+
import androidx.compose.ui.Alignment
31+
import androidx.compose.ui.Modifier
32+
import androidx.compose.ui.draw.clip
33+
import androidx.compose.ui.input.nestedscroll.nestedScroll
34+
import androidx.compose.ui.layout.ContentScale
35+
import androidx.compose.ui.platform.LocalConfiguration
36+
import androidx.compose.ui.platform.ViewCompositionStrategy
37+
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
38+
import androidx.compose.ui.res.colorResource
39+
import androidx.compose.ui.res.painterResource
40+
import androidx.compose.ui.res.stringResource
41+
import androidx.compose.ui.text.font.FontWeight
42+
import androidx.compose.ui.text.style.TextAlign
43+
import androidx.compose.ui.unit.dp
44+
import androidx.compose.ui.unit.sp
45+
import androidx.fragment.app.viewModels
46+
import androidx.lifecycle.ViewModel
47+
import androidx.lifecycle.lifecycleScope
48+
import androidx.recyclerview.widget.GridLayoutManager
49+
import com.habitrpg.android.habitica.R
50+
import com.habitrpg.android.habitica.data.InventoryRepository
51+
import com.habitrpg.android.habitica.databinding.FragmentComposeBinding
52+
import com.habitrpg.android.habitica.helpers.Analytics
53+
import com.habitrpg.android.habitica.helpers.AppConfigManager
54+
import com.habitrpg.android.habitica.models.inventory.Customization
55+
import com.habitrpg.android.habitica.models.inventory.Equipment
56+
import com.habitrpg.android.habitica.models.user.User
57+
import com.habitrpg.android.habitica.ui.adapter.CustomizationEquipmentRecyclerViewAdapter
58+
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
59+
import com.habitrpg.android.habitica.ui.theme.colors
60+
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
61+
import com.habitrpg.android.habitica.ui.views.PixelArtView
62+
import com.habitrpg.common.habitica.helpers.MainNavigationController
63+
import com.habitrpg.common.habitica.helpers.launchCatching
64+
import com.habitrpg.common.habitica.theme.HabiticaTheme
65+
import com.habitrpg.common.habitica.views.ComposableAvatarView
66+
import com.habitrpg.shared.habitica.models.Avatar
67+
import dagger.hilt.android.AndroidEntryPoint
68+
import kotlinx.coroutines.flow.combine
69+
import javax.inject.Inject
70+
71+
class AvatarEquipmentViewModel : ViewModel() {
72+
var type: String? = null
73+
var category: String? = null
74+
75+
val items = mutableStateListOf<Any>()
76+
val activeEquipment = mutableStateOf<String?>(null)
77+
78+
val typeNameId: Int
79+
get() =
80+
when (type) {
81+
"headAccessory" -> R.string.animal_ears
82+
"back" -> R.string.animal_tails
83+
else -> R.string.customizations
84+
}
85+
}
86+
87+
@AndroidEntryPoint
88+
class ComposeAvatarEquipmentFragment :
89+
BaseMainFragment<FragmentComposeBinding>() {
90+
private val viewModel: AvatarEquipmentViewModel by viewModels()
91+
92+
@Inject
93+
lateinit var inventoryRepository: InventoryRepository
94+
95+
@Inject
96+
lateinit var userViewModel: MainUserViewModel
97+
98+
@Inject
99+
lateinit var configManager: AppConfigManager
100+
101+
override var binding: FragmentComposeBinding? = null
102+
103+
override fun createBinding(
104+
inflater: LayoutInflater,
105+
container: ViewGroup?,
106+
): FragmentComposeBinding {
107+
return FragmentComposeBinding.inflate(inflater, container, false)
108+
}
109+
110+
override fun onCreateView(
111+
inflater: LayoutInflater,
112+
container: ViewGroup?,
113+
savedInstanceState: Bundle?,
114+
): View? {
115+
showsBackButton = true
116+
hidesToolbar = true
117+
val view = super.onCreateView(inflater, container, savedInstanceState)
118+
binding?.composeView?.apply {
119+
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
120+
setContent {
121+
HabiticaTheme {
122+
val activeEquipment by viewModel.activeEquipment
123+
val avatar by userViewModel.user.observeAsState()
124+
AvatarEquipmentView(avatar = avatar, configManager = configManager, viewModel.items, viewModel.type, stringResource(viewModel.typeNameId), activeEquipment) { equipment ->
125+
lifecycleScope.launchCatching {
126+
if (equipment.key?.isNotBlank() != true) {
127+
inventoryRepository.equip(viewModel.type ?: "", activeEquipment ?: "")
128+
} else {
129+
inventoryRepository.equip(
130+
equipment.type ?: "",
131+
equipment.key ?: "",
132+
)
133+
}
134+
}
135+
}
136+
}
137+
}
138+
}
139+
return view
140+
}
141+
142+
override fun onViewCreated(
143+
view: View,
144+
savedInstanceState: Bundle?,
145+
) {
146+
showsBackButton = true
147+
super.onViewCreated(view, savedInstanceState)
148+
arguments?.let {
149+
val args = ComposeAvatarEquipmentFragmentArgs.fromBundle(it)
150+
viewModel.type = args.type
151+
if (args.category.isNotEmpty()) {
152+
viewModel.category = args.category
153+
}
154+
}
155+
this.loadEquipment()
156+
157+
userViewModel.user.observe(viewLifecycleOwner) { updateUser(it) }
158+
159+
Analytics.sendNavigationEvent("${viewModel.type} screen")
160+
}
161+
162+
private fun loadEquipment() {
163+
val type = viewModel.type ?: return
164+
lifecycleScope.launchCatching {
165+
inventoryRepository.getEquipmentType(type, viewModel.category ?: "")
166+
.combine(inventoryRepository.getOwnedEquipment(type), ::Pair)
167+
.collect { (equipment, ownedEquipment) ->
168+
viewModel.items.clear()
169+
viewModel.items.addAll(equipment.filter {
170+
ownedEquipment.firstOrNull { owned -> owned.key == it.key } != null
171+
})
172+
}
173+
}
174+
}
175+
176+
fun updateUser(user: User?) {
177+
this.updateActiveCustomization(user)
178+
}
179+
180+
private fun updateActiveCustomization(user: User?) {
181+
if (viewModel.type == null || user?.preferences == null) {
182+
return
183+
}
184+
val outfit =
185+
if (user.preferences?.costume == true) user.items?.gear?.costume else user.items?.gear?.equipped
186+
val activeEquipment =
187+
when (viewModel.type) {
188+
"headAccessory" -> outfit?.headAccessory
189+
"back" -> outfit?.back
190+
"eyewear" -> outfit?.eyeWear
191+
else -> ""
192+
}
193+
if (activeEquipment != null) {
194+
viewModel.activeEquipment.value = activeEquipment
195+
}
196+
}
197+
}
198+
199+
200+
@Composable
201+
private fun AvatarEquipmentView(
202+
avatar: Avatar?,
203+
configManager: AppConfigManager,
204+
items: List<Any>,
205+
type: String?,
206+
typeName: String,
207+
activeCustomization: String?,
208+
onSelect: (Equipment) -> Unit,
209+
) {
210+
val nestedScrollInterop = rememberNestedScrollInteropConnection()
211+
val totalWidth = LocalConfiguration.current.screenWidthDp.dp
212+
val horizontalPadding = (totalWidth - (84.dp * 3)) / 2
213+
Column(horizontalAlignment = Alignment.CenterHorizontally) {
214+
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.background(colorResource(R.color.window_background))) {
215+
ComposableAvatarView(
216+
avatar = avatar,
217+
configManager = configManager,
218+
modifier =
219+
Modifier
220+
.padding(vertical = 24.dp)
221+
.size(140.dp, 147.dp),
222+
)
223+
Box(
224+
Modifier
225+
.background(colorResource(R.color.content_background), RoundedCornerShape(topStart = 22.dp, topEnd = 22.dp))
226+
.fillMaxWidth()
227+
.height(22.dp),
228+
)
229+
}
230+
LazyVerticalGrid(
231+
columns = GridCells.Adaptive(76.dp),
232+
horizontalArrangement = Arrangement.Center,
233+
contentPadding = PaddingValues(horizontal = horizontalPadding),
234+
modifier =
235+
Modifier
236+
.nestedScroll(nestedScrollInterop)
237+
.background(colorResource(R.color.content_background)),
238+
) {
239+
item(span = { GridItemSpan(3) }) {
240+
Text(
241+
typeName.uppercase(),
242+
fontSize = 14.sp,
243+
fontWeight = FontWeight.SemiBold,
244+
color = colorResource(id = R.color.text_ternary),
245+
textAlign = TextAlign.Center,
246+
modifier = Modifier.padding(10.dp),
247+
)
248+
}
249+
if (items.size > 1) {
250+
items(items, span = { item -> if (item is Customization) GridItemSpan(1) else GridItemSpan(3) }) { item ->
251+
if (item is Equipment) {
252+
Box(
253+
contentAlignment = Alignment.Center,
254+
modifier =
255+
Modifier
256+
.padding(4.dp)
257+
.border(if (activeCustomization == item.key) 2.dp else 0.dp, if (activeCustomization == item.key) HabiticaTheme.colors.tintedUiMain else colorResource(R.color.transparent), RoundedCornerShape(8.dp))
258+
.size(76.dp)
259+
.clip(RoundedCornerShape(8.dp))
260+
.clickable {
261+
onSelect(item)
262+
}
263+
.background(colorResource(id = R.color.window_background)),
264+
) {
265+
if (item.key.isNullOrBlank() || item.key == "0") {
266+
Image(painterResource(R.drawable.empty_slot), contentDescription = null, contentScale = ContentScale.None, modifier = Modifier.size(68.dp))
267+
} else {
268+
PixelArtView(
269+
imageName = item.key,
270+
Modifier.size(68.dp),
271+
)
272+
}
273+
}
274+
} else if (item is String) {
275+
Text(
276+
item.uppercase(),
277+
fontSize = 14.sp,
278+
fontWeight = FontWeight.SemiBold,
279+
color = colorResource(id = R.color.text_ternary),
280+
textAlign = TextAlign.Center,
281+
modifier = Modifier.padding(10.dp).padding(top = 16.dp),
282+
)
283+
}
284+
}
285+
}
286+
item(span = { GridItemSpan(3) }) {
287+
Column(
288+
horizontalAlignment = Alignment.CenterHorizontally,
289+
modifier =
290+
Modifier.padding(top = 56.dp).clickable {
291+
MainNavigationController.navigate(R.id.customizationsShopFragment)
292+
},
293+
) {
294+
Image(
295+
painterResource(if (type == "backgrounds") R.drawable.customization_background else R.drawable.customization_mix),
296+
null,
297+
modifier = Modifier.padding(bottom = 16.dp),
298+
)
299+
if (items.size <= 1) {
300+
Text(
301+
stringResource(R.string.customizations_no_owned), fontSize = 16.sp, fontWeight = FontWeight.Bold, color = colorResource(R.color.text_secondary),
302+
modifier = Modifier.padding(bottom = 2.dp))
303+
Text(stringResource(R.string.customization_shop_check_out), fontSize = 14.sp, color = colorResource(R.color.text_ternary), textAlign = TextAlign.Center)
304+
} else {
305+
Text(
306+
stringResource(R.string.looking_for_more), fontSize = 16.sp, fontWeight = FontWeight.Bold, color = colorResource(R.color.text_secondary),
307+
modifier = Modifier.padding(bottom = 2.dp))
308+
Text(stringResource(R.string.customization_shop_more), fontSize = 14.sp, color = colorResource(R.color.text_ternary), textAlign = TextAlign.Center)
309+
}
310+
}
311+
}
312+
}
313+
}
314+
}

version.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
NAME=4.3.7
2-
CODE=7611
2+
CODE=7631

0 commit comments

Comments
 (0)