Skip to content

Commit 3a90827

Browse files
committed
feat(athkar): long-press +10 increment and swipe-to-delete across all athkar cards
- Add incrementCountBy(id, 10) on long-press (400ms) to AthkarCard, MyAthkarCard, CustomAthkarCard - Replace Alert-based long-press delete with SwipeableAthkarCard swipe-to-delete in MyAthkarCategoryDetail and CustomAthkarDetail - Add LongPressHint banner in AthkarList, MyAthkarCategoryDetail, and CustomAthkarDetail - Delete SwipeableMyAthkarCard (superseded by generic SwipeableAthkarCard) - Bump version to 2.8.5
1 parent 8b1e440 commit 3a90827

16 files changed

Lines changed: 239 additions & 131 deletions

android/app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ android {
105105
minSdkVersion rootProject.ext.minSdkVersion
106106
targetSdkVersion rootProject.ext.targetSdkVersion
107107
versionCode 1
108-
versionName "2.7.5"
108+
versionName "2.8.5"
109109

110110
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
111111
}

app.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
},
1212
"name": "nedaa",
1313
"slug": "nedaa",
14-
"version": "2.7.5",
14+
"version": "2.8.5",
1515
"orientation": "portrait",
1616
"icon": "./assets/images/icon.png",
1717
"scheme": "myapp",

ios/nedaa/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
<key>CFBundlePackageType</key>
3333
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
3434
<key>CFBundleShortVersionString</key>
35-
<string>2.7.5</string>
35+
<string>2.8.5</string>
3636
<key>CFBundleSignature</key>
3737
<string>????</string>
3838
<key>CFBundleURLTypes</key>

src/components/athkar/AthkarCard.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ type Props = {
4242

4343
const AthkarCard: FC<Props> = ({ athkar, progress, onRequestOnboarding }) => {
4444
const { t, i18n } = useTranslation();
45-
const { incrementCount, decrementCount } = useAthkarStore();
45+
const { incrementCount, incrementCountBy, decrementCount } = useAthkarStore();
4646
const playerState = useAthkarStore((s) => s.playerState);
4747
const currentAthkarId = useAthkarStore((s) => s.currentAthkarId);
4848
const playbackMode = useAthkarAudioStore((s) => s.playbackMode);
@@ -53,6 +53,7 @@ const AthkarCard: FC<Props> = ({ athkar, progress, onRequestOnboarding }) => {
5353
const isThisPaused = currentAthkarId === athkar.id && playerState === "paused";
5454

5555
const hapticSelection = useHaptic("selection");
56+
const hapticMedium = useHaptic("medium");
5657
const hapticSuccess = useHaptic("success");
5758
const hapticWarning = useHaptic("warning");
5859

@@ -64,6 +65,7 @@ const AthkarCard: FC<Props> = ({ athkar, progress, onRequestOnboarding }) => {
6465
const isGrouped = !!athkar.group;
6566
const groupInfo = athkar.group;
6667
const currentGroupIndex = isGrouped ? currentCount % groupInfo!.itemsPerRound : 0;
68+
const displayTextKey = isGrouped ? groupInfo!.texts[currentGroupIndex] : athkar.text;
6769

6870
const handleIncrement = () => {
6971
hapticSelection();
@@ -79,9 +81,20 @@ const AthkarCard: FC<Props> = ({ athkar, progress, onRequestOnboarding }) => {
7981
}
8082
};
8183

84+
const handleLongPress = () => {
85+
if (isCompleted) return;
86+
hapticMedium();
87+
incrementCountBy(athkar.id, 10);
88+
if (currentCount + 10 >= total) {
89+
hapticSuccess();
90+
}
91+
};
92+
8293
return (
8394
<Pressable
8495
onPress={handleIncrement}
96+
onLongPress={handleLongPress}
97+
delayLongPress={400}
8598
disabled={isCompleted}
8699
accessibilityLabel={
87100
isCompleted
@@ -99,7 +112,7 @@ const AthkarCard: FC<Props> = ({ athkar, progress, onRequestOnboarding }) => {
99112
<VStack gap="$3">
100113
{/* Athkar Text */}
101114
<AthkarTextDisplay
102-
textKey={athkar.text}
115+
textKey={displayTextKey}
103116
size="xl"
104117
textAlign="left"
105118
color={isCompleted ? "$typographySecondary" : "$typography"}

src/components/athkar/AthkarList.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { Button } from "@/components/ui/button";
2323
import { Icon } from "@/components/ui/icon";
2424

2525
import AthkarCard from "@/components/athkar/AthkarCard";
26+
import LongPressHint from "@/components/athkar/LongPressHint";
2627

2728
// Store
2829
import { useAthkarStore } from "@/stores/athkar";
@@ -341,6 +342,8 @@ const AthkarList = ({ type, onRequestOnboarding }: Props) => {
341342
</Text>
342343
</Card>
343344
)}
345+
{/* Long-press hint */}
346+
{currentAthkarList.length > 0 && <LongPressHint />}
344347
{/* Athkar Cards */}
345348
{currentAthkarList.map((athkar) => {
346349
// Find progress using the athkar ID

src/components/athkar/CustomAthkarCard.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ type Props = {
2222
customItemId: number;
2323
arabicText: string;
2424
progress: CustomAthkarProgress;
25-
onLongPress?: () => void;
2625
};
2726

28-
const CustomAthkarCard: FC<Props> = ({ customItemId, arabicText, progress, onLongPress }) => {
27+
const CustomAthkarCard: FC<Props> = ({ customItemId, arabicText, progress }) => {
2928
const { t, i18n } = useTranslation();
30-
const { incrementCount, decrementCount } = useCustomAthkarStore();
29+
const { incrementCount, incrementCountBy, decrementCount } = useCustomAthkarStore();
3130
const hapticSelection = useHaptic("selection");
31+
const hapticMedium = useHaptic("medium");
3232
const hapticSuccess = useHaptic("success");
3333
const hapticWarning = useHaptic("warning");
3434

@@ -45,11 +45,20 @@ const CustomAthkarCard: FC<Props> = ({ customItemId, arabicText, progress, onLon
4545
}
4646
};
4747

48+
const handleLongPress = () => {
49+
if (completed) return;
50+
hapticMedium();
51+
incrementCountBy(customItemId, 10);
52+
if (currentCount + 10 >= totalCount) {
53+
hapticSuccess();
54+
}
55+
};
56+
4857
return (
4958
<Pressable
5059
onPress={handleIncrement}
51-
onLongPress={onLongPress}
52-
delayLongPress={500}
60+
onLongPress={handleLongPress}
61+
delayLongPress={400}
5362
disabled={completed}
5463
accessibilityRole="button"
5564
accessibilityLabel={

src/components/athkar/CustomAthkarDetail.tsx

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { FC, useCallback, useMemo } from "react";
2-
import { Alert } from "react-native";
1+
import { FC, useMemo } from "react";
32
import { useRouter } from "expo-router";
43
import { useTranslation } from "react-i18next";
54

@@ -12,8 +11,8 @@ import { Icon } from "@/components/ui/icon";
1211
import { ChevronRight, ChevronLeft, Pencil } from "lucide-react-native";
1312

1413
import { useCustomAthkarStore } from "@/stores/custom-athkar";
15-
import { useHaptic } from "@/hooks/useHaptic";
1614
import CustomAthkarCard from "@/components/athkar/CustomAthkarCard";
15+
import LongPressHint from "@/components/athkar/LongPressHint";
1716

1817
import type { CustomAthkarGroup } from "@/types/athkar";
1918

@@ -27,8 +26,6 @@ const CustomAthkarDetail: FC<Props> = ({ group, onBack }) => {
2726
const router = useRouter();
2827
const allItems = useCustomAthkarStore((s) => s.items);
2928
const allProgress = useCustomAthkarStore((s) => s.progress);
30-
const deleteGroup = useCustomAthkarStore((s) => s.deleteGroup);
31-
const hapticWarning = useHaptic("warning");
3229

3330
const isRTL = i18n.dir() === "rtl";
3431
const BackIcon = isRTL ? ChevronRight : ChevronLeft;
@@ -38,25 +35,6 @@ const CustomAthkarDetail: FC<Props> = ({ group, onBack }) => {
3835
return allProgress.filter((p) => itemIds.has(p.customItemId));
3936
}, [items, allProgress]);
4037

41-
const handleLongPress = useCallback(() => {
42-
hapticWarning();
43-
Alert.alert(
44-
t("athkar.customAthkar.deleteConfirm"),
45-
t("athkar.customAthkar.deleteWarning"),
46-
[
47-
{ text: t("athkar.customAthkar.cancel"), style: "cancel" },
48-
{
49-
text: t("athkar.customAthkar.delete"),
50-
style: "destructive",
51-
onPress: async () => {
52-
await deleteGroup(group.id);
53-
onBack();
54-
},
55-
},
56-
]
57-
);
58-
}, [deleteGroup, group.id, onBack, hapticWarning, t]);
59-
6038
return (
6139
<>
6240
{/* Header */}
@@ -93,6 +71,11 @@ const CustomAthkarDetail: FC<Props> = ({ group, onBack }) => {
9371
</Text>
9472
</VStack>
9573

74+
{/* Long-press hint */}
75+
<VStack marginBottom="$3">
76+
<LongPressHint />
77+
</VStack>
78+
9679
{/* Thikir Cards */}
9780
<VStack gap="$3">
9881
{items.map((item) => {
@@ -104,7 +87,6 @@ const CustomAthkarDetail: FC<Props> = ({ group, onBack }) => {
10487
customItemId={item.id}
10588
arabicText={item.arabicText}
10689
progress={prog}
107-
onLongPress={handleLongPress}
10890
/>
10991
);
11092
})}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { FC } from "react";
2+
import { useTranslation } from "react-i18next";
3+
4+
import { HStack } from "@/components/ui/hstack";
5+
import { Text } from "@/components/ui/text";
6+
import { Icon } from "@/components/ui/icon";
7+
8+
import { Lightbulb } from "lucide-react-native";
9+
10+
const LongPressHint: FC = () => {
11+
const { t } = useTranslation();
12+
13+
return (
14+
<HStack
15+
gap="$2"
16+
alignItems="center"
17+
paddingHorizontal="$3"
18+
paddingVertical="$2"
19+
borderRadius="$4"
20+
backgroundColor="$backgroundMuted"
21+
accessibilityRole="text">
22+
<Icon as={Lightbulb} size="xs" color="$typographySecondary" />
23+
<Text size="xs" color="$typographySecondary" flex={1}>
24+
{t("athkar.hints.longPressToTen")}
25+
</Text>
26+
</HStack>
27+
);
28+
};
29+
30+
export default LongPressHint;

src/components/athkar/MyAthkarCard.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ type Props = {
3232
categoryTitle: string;
3333
progress: MyAthkarProgress;
3434
onInfoPress: () => void;
35-
onLongPress?: () => void;
3635
};
3736

3837
const MyAthkarCard: FC<Props> = ({
@@ -41,11 +40,11 @@ const MyAthkarCard: FC<Props> = ({
4140
categoryTitle,
4241
progress,
4342
onInfoPress,
44-
onLongPress,
4543
}) => {
4644
const { t, i18n } = useTranslation();
47-
const { incrementCount, decrementCount } = useMyAthkarStore();
45+
const { incrementCount, incrementCountBy, decrementCount } = useMyAthkarStore();
4846
const hapticSelection = useHaptic("selection");
47+
const hapticMedium = useHaptic("medium");
4948
const hapticSuccess = useHaptic("success");
5049
const hapticWarning = useHaptic("warning");
5150

@@ -62,11 +61,20 @@ const MyAthkarCard: FC<Props> = ({
6261
}
6362
};
6463

64+
const handleLongPress = () => {
65+
if (completed) return;
66+
hapticMedium();
67+
incrementCountBy(myAthkarId, 10);
68+
if (currentCount + 10 >= totalCount) {
69+
hapticSuccess();
70+
}
71+
};
72+
6573
return (
6674
<Pressable
6775
onPress={handleIncrement}
68-
onLongPress={onLongPress}
69-
delayLongPress={500}
76+
onLongPress={handleLongPress}
77+
delayLongPress={400}
7078
disabled={completed}
7179
accessibilityRole="button"
7280
accessibilityLabel={

src/components/athkar/MyAthkarCategoryDetail.tsx

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { FC, useState, useCallback } from "react";
2-
import { Alert } from "react-native";
32
import { useTranslation } from "react-i18next";
43

54
// Components
@@ -20,6 +19,8 @@ import { useHaptic } from "@/hooks/useHaptic";
2019

2120
// Components
2221
import MyAthkarCard from "@/components/athkar/MyAthkarCard";
22+
import SwipeableAthkarCard from "@/components/athkar/SwipeableAthkarCard";
23+
import LongPressHint from "@/components/athkar/LongPressHint";
2324
import AthkarDetailSheet from "@/components/athkar/AthkarDetailSheet";
2425
import AthkarSearchSheet from "@/components/athkar/AthkarSearchSheet";
2526

@@ -58,24 +59,15 @@ const MyAthkarCategoryDetail: FC<Props> = ({
5859
setShowDetail(true);
5960
}, []);
6061

61-
const handleLongPress = useCallback(
62-
(myAthkarId: number) => {
62+
const handleDelete = useCallback(
63+
async (myAthkarId: number) => {
6364
hapticWarning();
64-
Alert.alert(t("athkar.myAthkar.remove"), t("athkar.myAthkar.removeConfirm"), [
65-
{ text: t("common.cancel"), style: "cancel" },
66-
{
67-
text: t("athkar.myAthkar.remove"),
68-
style: "destructive",
69-
onPress: async () => {
70-
await removeItem(myAthkarId);
71-
if (group.items.length <= 1) {
72-
onBack();
73-
}
74-
},
75-
},
76-
]);
65+
await removeItem(myAthkarId);
66+
if (group.items.length <= 1) {
67+
onBack();
68+
}
7769
},
78-
[removeItem, group.items.length, onBack, hapticWarning, t]
70+
[removeItem, group.items.length, onBack, hapticWarning]
7971
);
8072

8173
const selectedItem = selectedItemId ? group.items.find((i) => i.id === selectedItemId) : null;
@@ -106,6 +98,11 @@ const MyAthkarCategoryDetail: FC<Props> = ({
10698
</Text>
10799
</VStack>
108100

101+
{/* Long-press hint */}
102+
<VStack marginBottom="$3">
103+
<LongPressHint />
104+
</VStack>
105+
109106
{/* Athkar Cards */}
110107
<VStack gap="$3">
111108
{group.items.map((item) => {
@@ -115,15 +112,15 @@ const MyAthkarCategoryDetail: FC<Props> = ({
115112
if (!display || !prog) return null;
116113

117114
return (
118-
<MyAthkarCard
119-
key={item.id}
120-
myAthkarId={item.id}
121-
arabicText={display.arabicText}
122-
categoryTitle={isArabic ? display.categoryTitleAr : display.categoryTitleEn}
123-
progress={prog}
124-
onInfoPress={() => handleCardPress(item.id)}
125-
onLongPress={() => handleLongPress(item.id)}
126-
/>
115+
<SwipeableAthkarCard key={item.id} onDelete={() => handleDelete(item.id)}>
116+
<MyAthkarCard
117+
myAthkarId={item.id}
118+
arabicText={display.arabicText}
119+
categoryTitle={isArabic ? display.categoryTitleAr : display.categoryTitleEn}
120+
progress={prog}
121+
onInfoPress={() => handleCardPress(item.id)}
122+
/>
123+
</SwipeableAthkarCard>
127124
);
128125
})}
129126
</VStack>

0 commit comments

Comments
 (0)