Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
334 changes: 227 additions & 107 deletions src/containers/MainBody/ServerList/Item.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { t } from "i18next";
import { memo, useRef } from "react";
import { Pressable, StyleSheet, View } from "react-native";
import { memo, useRef, useState } from "react";
import { Pressable, StyleSheet, View, PanResponder } from "react-native";
import Icon from "../../../components/Icon";
import Text from "../../../components/Text";
import { images } from "../../../constants/images";
Expand All @@ -15,15 +15,98 @@ interface IProps {
index: number;
isSelected?: boolean;
onSelect?: (server: Server) => void;
isDraggable?: boolean;
onDragStart?: (server: Server, index: number) => void;
onDragEnd?: () => void;
onDragMove?: (index: number, y: number) => void;
isDraggedOver?: boolean;
isBeingDragged?: boolean;
}

const ServerItem = memo((props: IProps) => {
const { server, index } = props;
const {
server,
index,
isDraggable = false,
isDraggedOver = false,
isBeingDragged = false,
} = props;
const { theme, themeType } = useTheme();
const lastPressTime = useRef(0);
const { showPrompt, setServer } = useJoinServerPrompt();
const { show: showContextMenu } = useContextMenu();

const [isDragging, setIsDragging] = useState(false);
const dragStartTime = useRef(0);
const hasStartedDragging = useRef(false);

const panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => isDraggable,

onMoveShouldSetPanResponder: (_, gestureState) => {
if (!isDraggable) return false;

const distance = Math.sqrt(
gestureState.dx * gestureState.dx + gestureState.dy * gestureState.dy
);

if (hasStartedDragging.current) return true;

if (distance > 2) {
const isMainlyVertical =
Math.abs(gestureState.dy) > Math.abs(gestureState.dx) * 0.5;
return isMainlyVertical;
}

return false;
},

onPanResponderGrant: (evt) => {
if (isDraggable && props.onDragStart) {
hasStartedDragging.current = true;
setIsDragging(true);
document.body.style.cursor = "grabbing";
dragStartTime.current = Date.now();
props.onDragStart(server, index);

if (props.onDragMove) {
props.onDragMove(index, evt.nativeEvent.pageY);
}
}
},

onPanResponderMove: (evt) => {
if (isDraggable && hasStartedDragging.current && props.onDragMove) {
props.onDragMove(index, evt.nativeEvent.pageY);
}
},

onPanResponderRelease: () => {
if (isDraggable && hasStartedDragging.current) {
hasStartedDragging.current = false;
setIsDragging(false);
document.body.style.cursor = ""; // Add this
if (props.onDragEnd) {
props.onDragEnd();
}
}
},

onPanResponderTerminate: () => {
if (isDraggable && hasStartedDragging.current) {
hasStartedDragging.current = false;
setIsDragging(false);
document.body.style.cursor = ""; // Add this
if (props.onDragEnd) {
props.onDragEnd();
}
}
},

onShouldBlockNativeResponder: () =>
isDraggable && hasStartedDragging.current,
});

const onDoublePress = () => {
setServer(server);
showPrompt(true);
Expand Down Expand Up @@ -60,6 +143,8 @@ const ServerItem = memo((props: IProps) => {
};

const onPress = () => {
if (hasStartedDragging.current) return;

var delta = new Date().getTime() - lastPressTime.current;

if (delta < 500) {
Expand All @@ -74,135 +159,170 @@ const ServerItem = memo((props: IProps) => {
};

return (
<Pressable
key={"server-item-" + index}
style={styles.pressableContainer}
onPress={() => onPress()}
// @ts-ignore
onContextMenu={(e) => {
e.preventDefault();
showContextMenu({ x: e.clientX, y: e.clientY }, server);
return e;
}}
<View
style={[
styles.pressableContainer,
{
// // @ts-ignore
// cursor: isDragging || isDraggedOver ? "grabbing" : "default",
marginTop: 0,
opacity: isDragging || isBeingDragged ? 0.7 : 1,
transform: isDragging ? [{ translateY: -5 }] : [],
zIndex: isDragging ? 1000 : 1,
},
]}
{...(isDraggable ? panResponder.panHandlers : {})}
>
<View style={styles.serverContainer}>
{/* Drop indicator */}
{isDraggedOver && (
<View
style={[
styles.iconContainer,
{
backgroundColor: getServerStatusIconViewBackgroundColor(),
},
]}
>
<Icon
svg
title={getServerStatusIconTitle()}
image={
server.hasPassword ? images.icons.locked : images.icons.unlocked
}
size={sc(20)}
color={getServerStatusIconColor()}
/>
</View>
<View
id={
!props.isSelected
? themeType === "dark"
? "server-list-item-dark"
: "server-list-item-light"
: undefined
}
style={[
{
flexDirection: "row",
alignItems: "center",
flex: 1,
borderRadius: sc(5),
},
{
// borderWidth: props.isSelected ? 1 : 0,
borderColor: theme.primary,
backgroundColor: props.isSelected
? theme.primary + "7D"
: undefined,
},
]}
>
{server.usingOmp && (
<div
style={{
filter: `drop-shadow(0 0 8px ${theme.primary}CC)`,
}}
>
<View style={[styles.iconContainer, { marginHorizontal: sc(3) }]}>
<Icon
title={t("openmp_server")}
image={images.icons.omp}
size={sc(23)}
/>
</View>
</div>
)}
style={{
height: 2,
backgroundColor: theme.primary,
width: "100%",
marginBottom: 2,
}}
/>
)}

<Pressable
key={"server-item-" + index}
style={[
styles.pressableContainer,
{
// @ts-ignore
cursor: isDragging || isDraggedOver ? "grabbing" : "default",
},
]}
onPress={() => onPress()}
// @ts-ignore
onContextMenu={(e) => {
e.preventDefault();
showContextMenu({ x: e.clientX, y: e.clientY }, server);
return e;
}}
>
<View style={styles.serverContainer}>
<View
style={[
styles.commonFieldContainer,
styles.hostNameContainer,
{ paddingLeft: server.usingOmp ? 0 : sc(10) },
styles.iconContainer,
{
backgroundColor: getServerStatusIconViewBackgroundColor(),
// @ts-ignore
userSelect: "none",
},
]}
>
<Text style={{ fontSize: sc(17) }} color={theme.textPrimary}>
{server.hostname}
</Text>
<Icon
svg
title={getServerStatusIconTitle()}
image={
server.hasPassword ? images.icons.locked : images.icons.unlocked
}
size={sc(20)}
color={getServerStatusIconColor()}
/>
</View>
<View
style={{
flex: 0.5,
minWidth: 300,
flexDirection: "row",
marginLeft: server.usingOmp ? -26 : 0,
}}
id={
!props.isSelected
? themeType === "dark"
? "server-list-item-dark"
: "server-list-item-light"
: undefined
}
style={[
{
flexDirection: "row",
alignItems: "center",
flex: 1,
borderRadius: sc(5),
},
{
// borderWidth: props.isSelected ? 1 : 0,
borderColor: theme.primary,
backgroundColor: props.isSelected
? theme.primary + "7D"
: undefined,
},
]}
>
{server.usingOmp && (
<div
style={{
filter: `drop-shadow(0 0 8px ${theme.primary}CC)`,
}}
>
<View
style={[styles.iconContainer, { marginHorizontal: sc(3) }]}
>
<Icon
title={t("openmp_server")}
image={images.icons.omp}
size={sc(23)}
/>
</View>
</div>
)}
<View
style={[styles.commonFieldContainer, styles.pingFieldContainer]}
>
<Text style={{ fontSize: sc(17) }} color={theme.textSecondary}>
{server.ping === 9999 ? "-" : server.ping}
</Text>
</View>
<View
style={[styles.commonFieldContainer, styles.gameModeContainer]}
style={[
styles.commonFieldContainer,
styles.hostNameContainer,
{ paddingLeft: server.usingOmp ? 0 : sc(10) },
]}
>
<Text style={{ fontSize: sc(17) }} color={theme.textPrimary}>
{server.gameMode}
{server.hostname}
</Text>
</View>
<View
style={[
styles.commonFieldContainer,
styles.playersFieldContainer,
]}
style={{
flex: 0.5,
minWidth: 300,
flexDirection: "row",
marginLeft: server.usingOmp ? -26 : 0,
}}
>
<Text style={{ fontSize: sc(17) }} color={theme.textPrimary}>
{server.playerCount}
<Text
style={{ fontSize: sc(17) }}
color={theme.textPrimary + "AA"}
>
/{server.maxPlayers}
<View
style={[styles.commonFieldContainer, styles.pingFieldContainer]}
>
<Text style={{ fontSize: sc(17) }} color={theme.textSecondary}>
{server.ping === 9999 ? "-" : server.ping}
</Text>
</Text>
</View>
<View
style={[styles.commonFieldContainer, styles.gameModeContainer]}
>
<Text style={{ fontSize: sc(17) }} color={theme.textPrimary}>
{server.gameMode}
</Text>
</View>
<View
style={[
styles.commonFieldContainer,
styles.playersFieldContainer,
]}
>
<Text style={{ fontSize: sc(17) }} color={theme.textPrimary}>
{server.playerCount}
<Text
style={{ fontSize: sc(17) }}
color={theme.textPrimary + "AA"}
>
/{server.maxPlayers}
</Text>
</Text>
</View>
</View>
</View>
</View>
</View>
</Pressable>
</Pressable>
</View>
);
});

const styles = StyleSheet.create({
pressableContainer: {
// @ts-ignore
cursor: "default",
marginTop: sc(7),
},
serverContainer: {
Expand Down
Loading
Loading