11import { t } from "i18next" ;
2- import { memo , useRef } from "react" ;
3- import { Pressable , StyleSheet , View } from "react-native" ;
2+ import { memo , useRef , useState } from "react" ;
3+ import { Pressable , StyleSheet , View , PanResponder } from "react-native" ;
44import Icon from "../../../components/Icon" ;
55import Text from "../../../components/Text" ;
66import { images } from "../../../constants/images" ;
@@ -15,15 +15,98 @@ interface IProps {
1515 index : number ;
1616 isSelected ?: boolean ;
1717 onSelect ?: ( server : Server ) => void ;
18+ isDraggable ?: boolean ;
19+ onDragStart ?: ( server : Server , index : number ) => void ;
20+ onDragEnd ?: ( ) => void ;
21+ onDragMove ?: ( index : number , y : number ) => void ;
22+ isDraggedOver ?: boolean ;
23+ isBeingDragged ?: boolean ;
1824}
1925
2026const ServerItem = memo ( ( props : IProps ) => {
21- const { server, index } = props ;
27+ const {
28+ server,
29+ index,
30+ isDraggable = false ,
31+ isDraggedOver = false ,
32+ isBeingDragged = false ,
33+ } = props ;
2234 const { theme, themeType } = useTheme ( ) ;
2335 const lastPressTime = useRef ( 0 ) ;
2436 const { showPrompt, setServer } = useJoinServerPrompt ( ) ;
2537 const { show : showContextMenu } = useContextMenu ( ) ;
2638
39+ const [ isDragging , setIsDragging ] = useState ( false ) ;
40+ const dragStartTime = useRef ( 0 ) ;
41+ const hasStartedDragging = useRef ( false ) ;
42+
43+ const panResponder = PanResponder . create ( {
44+ onStartShouldSetPanResponder : ( ) => isDraggable ,
45+
46+ onMoveShouldSetPanResponder : ( _ , gestureState ) => {
47+ if ( ! isDraggable ) return false ;
48+
49+ const distance = Math . sqrt (
50+ gestureState . dx * gestureState . dx + gestureState . dy * gestureState . dy
51+ ) ;
52+
53+ if ( hasStartedDragging . current ) return true ;
54+
55+ if ( distance > 2 ) {
56+ const isMainlyVertical =
57+ Math . abs ( gestureState . dy ) > Math . abs ( gestureState . dx ) * 0.5 ;
58+ return isMainlyVertical ;
59+ }
60+
61+ return false ;
62+ } ,
63+
64+ onPanResponderGrant : ( evt ) => {
65+ if ( isDraggable && props . onDragStart ) {
66+ hasStartedDragging . current = true ;
67+ setIsDragging ( true ) ;
68+ document . body . style . cursor = "grabbing" ;
69+ dragStartTime . current = Date . now ( ) ;
70+ props . onDragStart ( server , index ) ;
71+
72+ if ( props . onDragMove ) {
73+ props . onDragMove ( index , evt . nativeEvent . pageY ) ;
74+ }
75+ }
76+ } ,
77+
78+ onPanResponderMove : ( evt ) => {
79+ if ( isDraggable && hasStartedDragging . current && props . onDragMove ) {
80+ props . onDragMove ( index , evt . nativeEvent . pageY ) ;
81+ }
82+ } ,
83+
84+ onPanResponderRelease : ( ) => {
85+ if ( isDraggable && hasStartedDragging . current ) {
86+ hasStartedDragging . current = false ;
87+ setIsDragging ( false ) ;
88+ document . body . style . cursor = "" ; // Add this
89+ if ( props . onDragEnd ) {
90+ props . onDragEnd ( ) ;
91+ }
92+ }
93+ } ,
94+
95+ onPanResponderTerminate : ( ) => {
96+ if ( isDraggable && hasStartedDragging . current ) {
97+ hasStartedDragging . current = false ;
98+ setIsDragging ( false ) ;
99+ document . body . style . cursor = "" ; // Add this
100+ if ( props . onDragEnd ) {
101+ props . onDragEnd ( ) ;
102+ }
103+ }
104+ } ,
105+
106+ onShouldBlockNativeResponder : ( ) =>
107+ isDraggable && hasStartedDragging . current ,
108+ } ) ;
109+
27110 const onDoublePress = ( ) => {
28111 setServer ( server ) ;
29112 showPrompt ( true ) ;
@@ -60,6 +143,8 @@ const ServerItem = memo((props: IProps) => {
60143 } ;
61144
62145 const onPress = ( ) => {
146+ if ( hasStartedDragging . current ) return ;
147+
63148 var delta = new Date ( ) . getTime ( ) - lastPressTime . current ;
64149
65150 if ( delta < 500 ) {
@@ -74,135 +159,170 @@ const ServerItem = memo((props: IProps) => {
74159 } ;
75160
76161 return (
77- < Pressable
78- key = { "server-item-" + index }
79- style = { styles . pressableContainer }
80- onPress = { ( ) => onPress ( ) }
81- // @ts -ignore
82- onContextMenu = { ( e ) => {
83- e . preventDefault ( ) ;
84- showContextMenu ( { x : e . clientX , y : e . clientY } , server ) ;
85- return e ;
86- } }
162+ < View
163+ style = { [
164+ styles . pressableContainer ,
165+ {
166+ // // @ts -ignore
167+ // cursor: isDragging || isDraggedOver ? "grabbing" : "default",
168+ marginTop : 0 ,
169+ opacity : isDragging || isBeingDragged ? 0.7 : 1 ,
170+ transform : isDragging ? [ { translateY : - 5 } ] : [ ] ,
171+ zIndex : isDragging ? 1000 : 1 ,
172+ } ,
173+ ] }
174+ { ...( isDraggable ? panResponder . panHandlers : { } ) }
87175 >
88- < View style = { styles . serverContainer } >
176+ { /* Drop indicator */ }
177+ { isDraggedOver && (
89178 < View
90- style = { [
91- styles . iconContainer ,
92- {
93- backgroundColor : getServerStatusIconViewBackgroundColor ( ) ,
94- } ,
95- ] }
96- >
97- < Icon
98- svg
99- title = { getServerStatusIconTitle ( ) }
100- image = {
101- server . hasPassword ? images . icons . locked : images . icons . unlocked
102- }
103- size = { sc ( 20 ) }
104- color = { getServerStatusIconColor ( ) }
105- />
106- </ View >
107- < View
108- id = {
109- ! props . isSelected
110- ? themeType === "dark"
111- ? "server-list-item-dark"
112- : "server-list-item-light"
113- : undefined
114- }
115- style = { [
116- {
117- flexDirection : "row" ,
118- alignItems : "center" ,
119- flex : 1 ,
120- borderRadius : sc ( 5 ) ,
121- } ,
122- {
123- // borderWidth: props.isSelected ? 1 : 0,
124- borderColor : theme . primary ,
125- backgroundColor : props . isSelected
126- ? theme . primary + "7D"
127- : undefined ,
128- } ,
129- ] }
130- >
131- { server . usingOmp && (
132- < div
133- style = { {
134- filter : `drop-shadow(0 0 8px ${ theme . primary } CC)` ,
135- } }
136- >
137- < View style = { [ styles . iconContainer , { marginHorizontal : sc ( 3 ) } ] } >
138- < Icon
139- title = { t ( "openmp_server" ) }
140- image = { images . icons . omp }
141- size = { sc ( 23 ) }
142- />
143- </ View >
144- </ div >
145- ) }
179+ style = { {
180+ height : 2 ,
181+ backgroundColor : theme . primary ,
182+ width : "100%" ,
183+ marginBottom : 2 ,
184+ } }
185+ />
186+ ) }
187+
188+ < Pressable
189+ key = { "server-item-" + index }
190+ style = { [
191+ styles . pressableContainer ,
192+ {
193+ // @ts -ignore
194+ cursor : isDragging || isDraggedOver ? "grabbing" : "default" ,
195+ } ,
196+ ] }
197+ onPress = { ( ) => onPress ( ) }
198+ // @ts -ignore
199+ onContextMenu = { ( e ) => {
200+ e . preventDefault ( ) ;
201+ showContextMenu ( { x : e . clientX , y : e . clientY } , server ) ;
202+ return e ;
203+ } }
204+ >
205+ < View style = { styles . serverContainer } >
146206 < View
147207 style = { [
148- styles . commonFieldContainer ,
149- styles . hostNameContainer ,
150- { paddingLeft : server . usingOmp ? 0 : sc ( 10 ) } ,
208+ styles . iconContainer ,
209+ {
210+ backgroundColor : getServerStatusIconViewBackgroundColor ( ) ,
211+ // @ts -ignore
212+ userSelect : "none" ,
213+ } ,
151214 ] }
152215 >
153- < Text style = { { fontSize : sc ( 17 ) } } color = { theme . textPrimary } >
154- { server . hostname }
155- </ Text >
216+ < Icon
217+ svg
218+ title = { getServerStatusIconTitle ( ) }
219+ image = {
220+ server . hasPassword ? images . icons . locked : images . icons . unlocked
221+ }
222+ size = { sc ( 20 ) }
223+ color = { getServerStatusIconColor ( ) }
224+ />
156225 </ View >
157226 < View
158- style = { {
159- flex : 0.5 ,
160- minWidth : 300 ,
161- flexDirection : "row" ,
162- marginLeft : server . usingOmp ? - 26 : 0 ,
163- } }
227+ id = {
228+ ! props . isSelected
229+ ? themeType === "dark"
230+ ? "server-list-item-dark"
231+ : "server-list-item-light"
232+ : undefined
233+ }
234+ style = { [
235+ {
236+ flexDirection : "row" ,
237+ alignItems : "center" ,
238+ flex : 1 ,
239+ borderRadius : sc ( 5 ) ,
240+ } ,
241+ {
242+ // borderWidth: props.isSelected ? 1 : 0,
243+ borderColor : theme . primary ,
244+ backgroundColor : props . isSelected
245+ ? theme . primary + "7D"
246+ : undefined ,
247+ } ,
248+ ] }
164249 >
250+ { server . usingOmp && (
251+ < div
252+ style = { {
253+ filter : `drop-shadow(0 0 8px ${ theme . primary } CC)` ,
254+ } }
255+ >
256+ < View
257+ style = { [ styles . iconContainer , { marginHorizontal : sc ( 3 ) } ] }
258+ >
259+ < Icon
260+ title = { t ( "openmp_server" ) }
261+ image = { images . icons . omp }
262+ size = { sc ( 23 ) }
263+ />
264+ </ View >
265+ </ div >
266+ ) }
165267 < View
166- style = { [ styles . commonFieldContainer , styles . pingFieldContainer ] }
167- >
168- < Text style = { { fontSize : sc ( 17 ) } } color = { theme . textSecondary } >
169- { server . ping === 9999 ? "-" : server . ping }
170- </ Text >
171- </ View >
172- < View
173- style = { [ styles . commonFieldContainer , styles . gameModeContainer ] }
268+ style = { [
269+ styles . commonFieldContainer ,
270+ styles . hostNameContainer ,
271+ { paddingLeft : server . usingOmp ? 0 : sc ( 10 ) } ,
272+ ] }
174273 >
175274 < Text style = { { fontSize : sc ( 17 ) } } color = { theme . textPrimary } >
176- { server . gameMode }
275+ { server . hostname }
177276 </ Text >
178277 </ View >
179278 < View
180- style = { [
181- styles . commonFieldContainer ,
182- styles . playersFieldContainer ,
183- ] }
279+ style = { {
280+ flex : 0.5 ,
281+ minWidth : 300 ,
282+ flexDirection : "row" ,
283+ marginLeft : server . usingOmp ? - 26 : 0 ,
284+ } }
184285 >
185- < Text style = { { fontSize : sc ( 17 ) } } color = { theme . textPrimary } >
186- { server . playerCount }
187- < Text
188- style = { { fontSize : sc ( 17 ) } }
189- color = { theme . textPrimary + "AA" }
190- >
191- /{ server . maxPlayers }
286+ < View
287+ style = { [ styles . commonFieldContainer , styles . pingFieldContainer ] }
288+ >
289+ < Text style = { { fontSize : sc ( 17 ) } } color = { theme . textSecondary } >
290+ { server . ping === 9999 ? "-" : server . ping }
192291 </ Text >
193- </ Text >
292+ </ View >
293+ < View
294+ style = { [ styles . commonFieldContainer , styles . gameModeContainer ] }
295+ >
296+ < Text style = { { fontSize : sc ( 17 ) } } color = { theme . textPrimary } >
297+ { server . gameMode }
298+ </ Text >
299+ </ View >
300+ < View
301+ style = { [
302+ styles . commonFieldContainer ,
303+ styles . playersFieldContainer ,
304+ ] }
305+ >
306+ < Text style = { { fontSize : sc ( 17 ) } } color = { theme . textPrimary } >
307+ { server . playerCount }
308+ < Text
309+ style = { { fontSize : sc ( 17 ) } }
310+ color = { theme . textPrimary + "AA" }
311+ >
312+ /{ server . maxPlayers }
313+ </ Text >
314+ </ Text >
315+ </ View >
194316 </ View >
195317 </ View >
196318 </ View >
197- </ View >
198- </ Pressable >
319+ </ Pressable >
320+ </ View >
199321 ) ;
200322} ) ;
201323
202324const styles = StyleSheet . create ( {
203325 pressableContainer : {
204- // @ts -ignore
205- cursor : "default" ,
206326 marginTop : sc ( 7 ) ,
207327 } ,
208328 serverContainer : {
0 commit comments