diff --git a/app/(app)/entities/index.tsx b/app/(app)/entities/index.tsx
index 0778aba..75e0fc3 100644
--- a/app/(app)/entities/index.tsx
+++ b/app/(app)/entities/index.tsx
@@ -2,7 +2,6 @@ import { router } from "expo-router";
import { useState } from "react";
import {
Alert,
- Pressable,
RefreshControl,
ScrollView,
StyleSheet,
@@ -155,23 +154,22 @@ export default function EntitiesScreen() {
onPress: () => confirmDelete(client.id, client.name),
},
]}
+ onPress={() => router.push(`/(app)/entities/clients/${client.id}`)}
>
- router.push(`/(app)/entities/clients/${client.id}`)}>
-
-
- {client.name}
- {client.email ? (
- {client.email}
- ) : null}
- {client.defaultHourlyRate != null ? (
-
- {formatCurrency(client.defaultHourlyRate, client.currency ?? "USD")}
- /hr
-
- ) : null}
-
-
-
+
+
+ {client.name}
+ {client.email ? (
+ {client.email}
+ ) : null}
+ {client.defaultHourlyRate != null ? (
+
+ {formatCurrency(client.defaultHourlyRate, client.currency ?? "USD")}
+ /hr
+
+ ) : null}
+
+
))
)
@@ -205,23 +203,22 @@ export default function EntitiesScreen() {
onPress: () => confirmDelete(business.id, business.name),
},
]}
+ onPress={() => router.push(`/(app)/entities/businesses/${business.id}`)}
>
- router.push(`/(app)/entities/businesses/${business.id}`)}>
-
-
-
- {business.name}
- {business.isDefault ? (
- Default
- ) : null}
-
- {business.nickname ? (
- {business.nickname}
+
+
+
+ {business.name}
+ {business.isDefault ? (
+ Default
) : null}
- {business.email ? {business.email} : null}
-
-
+ {business.nickname ? (
+ {business.nickname}
+ ) : null}
+ {business.email ? {business.email} : null}
+
+
))
)}
diff --git a/app/(app)/invoices/index.tsx b/app/(app)/invoices/index.tsx
index f83a9d2..cc6ff6e 100644
--- a/app/(app)/invoices/index.tsx
+++ b/app/(app)/invoices/index.tsx
@@ -2,7 +2,6 @@ import { router } from "expo-router";
import { useState } from "react";
import {
Alert,
- Pressable,
RefreshControl,
ScrollView,
StyleSheet,
@@ -205,31 +204,32 @@ export default function InvoicesScreen() {
];
return (
-
- router.push(`/(app)/invoices/${invoice.id}`)}
- onLongPress={() => promptStatusChange(invoice.id, status)}
- >
-
-
-
-
- {label}
-
- {invoice.client?.name ?? "Client"}
-
-
-
- {formatCurrency(invoice.totalAmount, invoice.currency)}
+ router.push(`/(app)/invoices/${invoice.id}`)}
+ onLongPress={() => promptStatusChange(invoice.id, status)}
+ >
+
+
+
+
+ {label}
+
+ {invoice.client?.name ?? "Client"}
-
- Due {formatDate(invoice.dueDate)}
-
-
+
+ {formatCurrency(invoice.totalAmount, invoice.currency)}
+
-
-
+
+ Due {formatDate(invoice.dueDate)}
+
+
+
+
);
})
diff --git a/app/(app)/more/expenses/index.tsx b/app/(app)/more/expenses/index.tsx
index 190ddbc..dd5eb74 100644
--- a/app/(app)/more/expenses/index.tsx
+++ b/app/(app)/more/expenses/index.tsx
@@ -2,7 +2,6 @@ import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
import { useMemo, useState } from "react";
import {
- Pressable,
RefreshControl,
ScrollView,
StyleSheet,
@@ -225,6 +224,12 @@ function ExpenseRow({ expense, onDelete }: { expense: Expense; onDelete: () => v
return (
[
+ styles.row,
+ { borderColor: colors.border },
+ pressed && styles.rowPressed,
+ ]}
+ onPress={() => router.push(`/(app)/more/expenses/${expense.id}` as never)}
actions={[
{
key: "open",
@@ -244,67 +249,57 @@ function ExpenseRow({ expense, onDelete }: { expense: Expense; onDelete: () => v
},
]}
>
- router.push(`/(app)/more/expenses/${expense.id}` as never)}
- style={({ pressed }) => [
- styles.row,
- { borderColor: colors.border },
- pressed && styles.rowPressed,
- ]}
- >
-
-
+
+
+
+
+
+
+ {expense.description}
+
+ {expense.receiptCount ? (
+
+
+
+ {expense.receiptCount}
+
+
+ ) : null}
-
-
-
- {expense.description}
+
+ {formatDate(expense.date)}
+ {expense.category ? ` · ${expense.category}` : ""}
+ {expense.client?.name ? ` · ${expense.client.name}` : ""}
+
+
+ {expense.billable ? (
+
+ Billable
- {expense.receiptCount ? (
-
-
-
- {expense.receiptCount}
-
-
- ) : null}
-
-
- {formatDate(expense.date)}
- {expense.category ? ` · ${expense.category}` : ""}
- {expense.client?.name ? ` · ${expense.client.name}` : ""}
-
-
- {expense.billable ? (
-
- Billable
-
- ) : null}
- {expense.reimbursable ? (
-
- Reimbursable
-
- ) : null}
- {expense.taxDeductible ? (
-
- Tax
-
- ) : null}
-
+ ) : null}
+ {expense.reimbursable ? (
+
+ Reimbursable
+
+ ) : null}
+ {expense.taxDeductible ? (
+
+ Tax
+
+ ) : null}
-
-
- {formatCurrency(expense.amount, expense.currency)}
-
-
-
-
+
+
+
+ {formatCurrency(expense.amount, expense.currency)}
+
+
+
);
}
diff --git a/components/SwipeableRow.tsx b/components/SwipeableRow.tsx
index f686c9c..f7fa2fa 100644
--- a/components/SwipeableRow.tsx
+++ b/components/SwipeableRow.tsx
@@ -1,6 +1,6 @@
import { Ionicons } from "@expo/vector-icons";
import { ReactNode, useRef } from "react";
-import { Pressable, StyleSheet, Text, View } from "react-native";
+import { Pressable, type PressableProps, StyleSheet, Text, View } from "react-native";
import Swipeable, {
type SwipeableMethods,
} from "react-native-gesture-handler/ReanimatedSwipeable";
@@ -24,6 +24,9 @@ type SwipeableRowProps = {
actions: SwipeAction[];
enabled?: boolean;
backgroundColor?: string;
+ onPress?: () => void;
+ onLongPress?: () => void;
+ contentStyle?: PressableProps["style"];
};
export function SwipeableRow({
@@ -31,11 +34,63 @@ export function SwipeableRow({
actions,
enabled = true,
backgroundColor,
+ onPress,
+ onLongPress,
+ contentStyle,
}: SwipeableRowProps) {
const { colors } = useAppTheme();
const styles = useThemedStyles(createSwipeableRowStyles);
const rowBackground = backgroundColor ?? colors.background;
const swipeRef = useRef(null);
+ const rowOpenRef = useRef(false);
+ const suppressPressUntilRef = useRef(0);
+
+ function suppressContentPress() {
+ suppressPressUntilRef.current = Date.now() + 350;
+ }
+
+ function handleContentPress() {
+ if (!onPress) return;
+
+ if (rowOpenRef.current || Date.now() < suppressPressUntilRef.current) {
+ swipeRef.current?.close();
+ rowOpenRef.current = false;
+ return;
+ }
+
+ onPress();
+ }
+
+ function renderContent() {
+ if (!onPress && !onLongPress) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ return (
+ [
+ styles.row,
+ { backgroundColor: rowBackground },
+ typeof contentStyle === "function" ? contentStyle(state) : contentStyle,
+ ]}
+ >
+ {children}
+
+ );
+ }
function renderRightActions() {
return (
@@ -45,7 +100,9 @@ export function SwipeableRow({
key={action.key}
style={[styles.actionButton, { backgroundColor: action.backgroundColor }]}
onPress={() => {
+ suppressContentPress();
swipeRef.current?.close();
+ rowOpenRef.current = false;
action.onPress();
}}
>
@@ -58,12 +115,26 @@ export function SwipeableRow({
}
if (!enabled || actions.length === 0) {
- return {children};
+ return renderContent();
}
return (
-
- {children}
+ {
+ suppressContentPress();
+ rowOpenRef.current = true;
+ }}
+ onSwipeableWillClose={suppressContentPress}
+ onSwipeableClose={() => {
+ rowOpenRef.current = false;
+ }}
+ >
+ {renderContent()}
);
}