From 2c5337f3dc1f445e5ed9a09725e41b3576afdcf8 Mon Sep 17 00:00:00 2001 From: "Andres Gomez, Thomas (ITDV RL)" Date: Wed, 21 Aug 2024 11:32:11 +0200 Subject: [PATCH] =?UTF-8?q?Add=20haptic=20feedback=20(thx=20J=C3=A9r=C3=A9?= =?UTF-8?q?my).=20better=20handling=20of=20removal=20animation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/inventory/InventoryFactory.kt | 3 +- .../pages/inventory/InventoryPage.kt | 14 ++++- .../pages/inventory/InventoryViewModel.kt | 54 ++++++++++++++---- .../rplexicon/utilitary/ImageCache.kt | 2 + .../main/res/drawable/ic_drive_bear_trap.webp | Bin 0 -> 5382 bytes .../res/drawable/ic_drive_chalk_stick.webp | Bin 0 -> 2194 bytes 6 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 app/src/main/res/drawable/ic_drive_bear_trap.webp create mode 100644 app/src/main/res/drawable/ic_drive_chalk_stick.webp diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/inventory/InventoryFactory.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/inventory/InventoryFactory.kt index c52ef6f..442860e 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/inventory/InventoryFactory.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/inventory/InventoryFactory.kt @@ -62,9 +62,10 @@ class InventoryFactory @Inject constructor() { items: Map, fires: List, order: Map, + parent: Map, ): Map> { return fires - .groupBy { it.parentId } + .groupBy { parent[it.id] } .mapValues { (_, fires) -> fires .asSequence() diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/inventory/InventoryPage.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/inventory/InventoryPage.kt index cde397b..aab401b 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/inventory/InventoryPage.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/inventory/InventoryPage.kt @@ -31,6 +31,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp @@ -70,6 +71,7 @@ fun InventoryPage( containerWidth: Dp = 64.dp, ) { val overlay = LocalRollOverlay.current + val feedback = LocalHapticFeedback.current val scope = rememberCoroutineScope() val selectedContainer = inventoryViewModel.selectedContainer @@ -78,10 +80,16 @@ fun InventoryPage( val dragDropState = rememberInventoryDragDropState( containerWidth = containerWidth, - isItemDraggable = inventoryViewModel::isItemDraggable, + isItemDraggable = { + inventoryViewModel.isItemDraggable(it, feedback) + }, isItemsMovable = inventoryViewModel::isItemsMovable, - onItemMove = inventoryViewModel::onItemMove, - onItemRelease = inventoryViewModel::onItemRelease, + onItemMove = { from, to -> + inventoryViewModel.onItemMove(from, to, feedback) + }, + onItemRelease = { from, to -> + inventoryViewModel.onItemRelease(from, to, feedback) + }, onItemLongClick = { // on container long press we want to display the detail dialog. if (it.type == Type.CONTAINER) { diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/inventory/InventoryViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/inventory/InventoryViewModel.kt index d53e2b0..2a359c5 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/inventory/InventoryViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/inventory/InventoryViewModel.kt @@ -5,6 +5,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState +import androidx.compose.ui.hapticfeedback.HapticFeedback +import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -41,6 +43,7 @@ class InventoryViewModel @Inject constructor( private val character = savedStateHandle.characterSheetArgument.name private val orderOverrider = MutableStateFlow>(emptyMap()) + private val parentOverrider = MutableStateFlow>(emptyMap()) private val _selectedContainerId = MutableStateFlow(null) private val _containers: StateFlow> = combine( @@ -77,6 +80,7 @@ class InventoryViewModel @Inject constructor( itemRepository.data, fireRepository.getInventory(character), orderOverrider, + parentOverrider, transform = inventoryFactory::fromModelsToItemUio, ).stateIn( scope = viewModelScope, @@ -88,6 +92,24 @@ class InventoryViewModel @Inject constructor( @Stable get() = _items.collectAsState() + + init { + viewModelScope.launch { + launch(Dispatchers.Default) { + // Listen to fire repository to update the orderOverrider as soon as firebase is updated. + fireRepository.getInventory(character = character).collect { fires -> + orderOverrider.value = fires.associate { fire -> fire.id to fire.order } + } + } + launch(Dispatchers.Default) { + // Listen to fire repository to update the orderOverrider as soon as firebase is updated. + fireRepository.getInventory(character = character).collect { fires -> + parentOverrider.value = fires.associate { fire -> fire.id to fire.parentId } + } + } + } + } + fun getItem(element: Element): Any? { return when (element.type) { Type.CONTAINER -> _containers.value.getOrNull(element.index) @@ -99,11 +121,17 @@ class InventoryViewModel @Inject constructor( _selectedContainerId.value = id } - fun isItemDraggable(element: Element): Boolean { + fun isItemDraggable(element: Element, feedback: HapticFeedback): Boolean { Log.d("InventoryViewModel", "isItemDraggable(element:${element}") return when (val item = getItem(element = element)) { - is InventoryItemUio -> true - is InventoryContainerUio -> item.id != null + is InventoryItemUio -> { + feedback.performHapticFeedback(HapticFeedbackType.LongPress) + true + } + is InventoryContainerUio -> { + feedback.performHapticFeedback(HapticFeedbackType.LongPress) + item.id != null + } else -> false } } @@ -135,7 +163,7 @@ class InventoryViewModel @Inject constructor( } } - fun onItemMove(from: Element, to: Element) { + fun onItemMove(from: Element, to: Element, feedback: HapticFeedback) { if (from != to) { Log.d("InventoryViewModel", "onItemMove(from:${from}, to:${to})") val fromItem: Any? = getItem(element = from) @@ -146,6 +174,8 @@ class InventoryViewModel @Inject constructor( val refFromItem = itemRepository.find(id = fromItem.id) val refToItem = itemRepository.find(id = toItem.id) if (refFromItem?.type != null && refFromItem.type == refToItem?.type) { + // perform feedback + feedback.performHapticFeedback(HapticFeedbackType.TextHandleMove) // reorder locally orderOverrider.value = orderOverrider.value.toMutableMap().also { it[fromItem.id] = toItem.order @@ -168,6 +198,8 @@ class InventoryViewModel @Inject constructor( val refFromItem = itemRepository.find(id = fromItem.id) val refToItem = itemRepository.find(id = toItem.id) if (refFromItem?.type != null && refFromItem.type == refToItem?.type) { + // perform feedback + feedback.performHapticFeedback(HapticFeedbackType.TextHandleMove) // reorder locally orderOverrider.value = orderOverrider.value.toMutableMap().also { it[fromItem.id] = toItem.order @@ -189,9 +221,7 @@ class InventoryViewModel @Inject constructor( } } - fun onItemRelease(from: Element, to: Element) { - // clean the order override map, this is only needed when moving items around. - orderOverrider.value = emptyMap() + fun onItemRelease(from: Element, to: Element, feedback: HapticFeedback) { // only do something when an item is release on top of a container. if (from.type == Type.ITEM && to.type == Type.CONTAINER) { // fetch the itemId & containerId. @@ -215,7 +245,12 @@ class InventoryViewModel @Inject constructor( val typedOrderContainerFires = fires.filter { typedItems[it.id] != null && it.parentId == containerId } - + parentOverrider.value = parentOverrider.value.toMutableMap().also { override -> + override[fire.id] = containerId + } + // perform feedback + feedback.performHapticFeedback(HapticFeedbackType.TextHandleMove) + // update the data layer. viewModelScope.launch(Dispatchers.IO) { // remove the item from the list fireRepository.removeItem( @@ -247,9 +282,6 @@ class InventoryViewModel @Inject constructor( defaultParentId: String? = _selectedContainerId.value, quantity: Int, ) { - // clean the order override map, this is only needed when moving items around. - orderOverrider.value = emptyMap() - val item = itemRepository.find(id = itemId) // check the new quantity to decide if we update or remove the item diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/ImageCache.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/ImageCache.kt index d1bf243..f5a077f 100644 --- a/app/src/main/java/com/pixelized/rplexicon/utilitary/ImageCache.kt +++ b/app/src/main/java/com/pixelized/rplexicon/utilitary/ImageCache.kt @@ -106,12 +106,14 @@ object ImageCache { "https://drive.google.com/uc?export=view&id=1Dc0hBGXTNP8dnYqHJlSsK1WgWAsWtCV9" to R.drawable.ic_drive_lantern_of_revealing, "https://drive.google.com/uc?export=view&id=1FX7HpnxLUXFPqrKGvCYyJNkUSwFacKh9" to R.drawable.ic_silver_coin_pile_unfaded, "https://drive.google.com/uc?export=view&id=1jJkltHU5HzCuU5ixVly_J5Dr0stEawEV" to R.drawable.ic_drive_deck_of_card, + "https://drive.google.com/uc?export=view&id=1JhUikzOesAyGCtCL9bFW_HRdqM8hj1sf" to R.drawable.ic_drive_bear_trap, "https://drive.google.com/uc?export=view&id=1Jo0Nk_Mj4j-VKvBe0W6_73uE_tKUgPUF" to R.drawable.ic_drive_kitsune, "https://drive.google.com/uc?export=view&id=1KPlgQK1C3lfZn3ZHTHd-dqiSINjQpnH9" to R.drawable.ic_drive_ring_of_kazan, "https://drive.google.com/uc?export=view&id=1KVXSKHI8JgU1Wnhp9rmEZ1f4rsUfoV2a" to R.drawable.ic_gold_coin_pile_unfaded, "https://drive.google.com/uc?export=view&id=1lLu7o1h1pkXuNRbHJ2dnxQLKyrNRxV3P" to R.drawable.ic_drive_bear_claw_neckless, "https://drive.google.com/uc?export=view&id=1nazdbGFLyiGNMYvPhVXiZoeyBxDtYMtI" to R.drawable.ic_electrum_coin_pile_unfaded, "https://drive.google.com/uc?export=view&id=1p5OSYPeeKa-niiTa9GFChdpnu0FqkswG" to R.drawable.ic_drive_harlequin, + "https://drive.google.com/uc?export=view&id=1TDTCkVZb520gdndC4FmAneWDGgf9wmDM" to R.drawable.ic_drive_chalk_stick, "https://drive.google.com/uc?export=view&id=1PlhfW61aSncyX3Ml1oPDPokHTR7U8O2y" to R.drawable.ic_drive_quiver, "https://drive.google.com/uc?export=view&id=1WSpydN6AVjQrS6J_F8EFT00McQqCCxCw" to R.drawable.ic_drive_stake, "https://drive.google.com/uc?export=view&id=1ywDHw0C6exwxNCu-qNgQ0X2Sx9LfqSNH" to R.drawable.ic_drive_bird_skull, diff --git a/app/src/main/res/drawable/ic_drive_bear_trap.webp b/app/src/main/res/drawable/ic_drive_bear_trap.webp new file mode 100644 index 0000000000000000000000000000000000000000..2c8a3f1228ff87d82fb7843b009d8780e33df44f GIT binary patch literal 5382 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_drive_chalk_stick.webp b/app/src/main/res/drawable/ic_drive_chalk_stick.webp new file mode 100644 index 0000000000000000000000000000000000000000..d69537c4982af167c880a4b8ba93e4f190d0d884 GIT binary patch literal 2194 zcmWIYbaU(CU|zS=L)8!vus&#i^QQ1CG#&hCf$DHXmXa8sXH@|4r zuDP2|xOH(g9}lUlxUh}2{BG)Z-{+Fz4s#oW7Vl|ne)jnDHHLuY`In|k&MBVvcF)f0 zPyD=UmF>(E-yTW0y)^uKuKcof4-FpcR^I*bVrl-3M?WqKyRp~Jqd>o2RuKTsu6lS^4{BkKWkQOoW94pF=(@}E@(!q z)7Py1=`O65^I|w{&*m)=-99nzd+7!9t9c5_ulTA?^sR{b*|6il-jLZJDl-i~2uN(@ zQLEW3T7c>Tzu7g0;2Y|s2-U2AZGSuVvEaM(epF+zvc;hb*TGs*0z;4 zr~TEg?Ac}e@@TDDZd%Q{885kG4YI1u?SjvwxCJJ^zjLGAP`_{a^k)_Z>{Y8=E4x>* zUgq6C& z?=%eFJ?D937Q~~v?9ar&um|fT*k>76Cy10eR6CWXmG*4nbK$)^b8)S3+5(&4zdAE! zFo;OJ=VmjU=e~9It7h{fLT@kpKb!IYgJ6~6^6jzpJm;2AEn+*R)Oh8_Tr>56qZz~U`E;$Pby=wP!s}f7HwnJb^yM$EIg$2d`pz#5Y>)4reBnOxVRcZq zMe3GtlRYw_RSlbtOnQHL_r)EYuAM5!55)Ld*)N*ixG?C>)>pwxz8>Cfo9J+Hn(w_= z`4Y$eIC)Z zlcP>-5aLYdDBypoe(ALeE?V7q(6%gxW9{DW^{E`LZzOf1`!;Plyz15_Wu^yC%T8RL$ejDz z@bwQ)<&=NIGKS+q@a+G%O_#aeH7*Iqmye0v+~kqLnlSg%ag5*2x|*eKCLc3b>MaB6>njd`Cpu7Pq*C2&}EkXczbJJ(*OL8XP;X?@Ge@az^$WVo^!VO zY}FN+wX04||EZ;K|K*~ULU(X{n-WK)(rh>Bc9x6X|E>a z)XMEJkll6Cc6vAmH=8Pl#X@~y6PD>eY9G#B>o_O$aE0LK1aS$aHNLwd(-Wp%k!=kN zpO&|6)5SfZQL8^rpObC&LhX3&yfq%yI{xDE*UPPA3o?%~K6sI;=KFWqW~Y`zr`E~* z>U4WJZ;D)27{%IT+iit9oR)|CYm>zQxaAG|yc0wsnR`VPZz*fkUfpBH0jd{#nM~2H z2i`9|++M{{6Q1@W!P$tXl1)R8`__XAA46OH+IYTv%~uJ3VY0CL|GD@BHQc9S6XUiT zAG*16x&QRE=^E-q+CnFPCLCbc9~$s@Mdv9its9(IwVrlm@8b2-opZy0Rl@uKx1dGy z!kQNtc<4J@G0v#aJ9BXLMf1XU{7y@?l+7e#yGyfn$Nj%rnYYPEG5HVs=@!0~A2az% zn`U@&zIeM$sV&zswX8{DFEnn?$Xw3(;a=&=MZ!+nWuBUUeQWwx6^SIO zwEXbnkCk1_&s4Q%l3>SygMK#*zn?Ja?iXob%208-cR}#L1~bNp*Ufe zhcaF>ZJ5Hcc(UM&Iu1rb*X{S%LkoFAx-Wj8s-gP$uF(_oILR|xjHZ>|d;HRO$#;R9 z0lili*lync`FHt?t!s6IyWESTV~?^vF}{)*eQkT!|9JVYTiF!-JS+O$?|;0KYoh;X zW2FW|srQ7YmG!fnZ~7H(IiP;zs`XNpF8M777cO9Sxuv09)WoUtLGb(DvS;4e@oQw= zXLJ`fF6lncv#MO}Rm)>JEg>$q0QTafC(I2q3+=O>Z;7v7X2ku&Ao%|tA)apxtNXb0 Zq?_`B(q=PrZ1npw(P91T#sUTg1_0G;H6Z{1 literal 0 HcmV?d00001