Skip to content

Commit a8c6b35

Browse files
committed
feat(app): integrate pressto
1 parent c87395b commit a8c6b35

7 files changed

Lines changed: 112 additions & 85 deletions

File tree

bun.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
"expo-video": "~3.0.11",
137137
"express": "^5.1.0",
138138
"jest-junit": "^16.0.0",
139+
"pressto": "^0.6.0",
139140
"react": "19.1.0",
140141
"react-dom": "19.1.0",
141142
"react-native": "0.81.4",

src/Providers/Providers.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { useReactNavigationDevTools } from '@rozenite/react-navigation-plugin';
1616
import { useTanStackQueryDevTools } from '@rozenite/tanstack-query-plugin';
1717
import { useQueryClient } from '@tanstack/react-query';
1818
import * as Clipboard from 'expo-clipboard';
19+
import * as Haptics from 'expo-haptics';
20+
import { PressablesConfig } from 'pressto';
1921
import { PropsWithChildren } from 'react';
2022
import { GestureHandlerRootView } from 'react-native-gesture-handler';
2123
import { KeyboardProvider } from 'react-native-keyboard-controller';
@@ -93,7 +95,18 @@ export function Providers({ children }: PropsWithChildren) {
9395
<KeyboardProvider>
9496
<GestureHandlerRootView style={styles.gestureContainer}>
9597
<BottomSheetModalProvider>
96-
<QueryProviderWithAuth>{children}</QueryProviderWithAuth>
98+
<QueryProviderWithAuth>
99+
<PressablesConfig
100+
globalHandlers={{
101+
onPress: () => {
102+
Haptics.selectionAsync();
103+
},
104+
}}
105+
>
106+
{' '}
107+
{children}
108+
</PressablesConfig>
109+
</QueryProviderWithAuth>
97110
</BottomSheetModalProvider>
98111
</GestureHandlerRootView>
99112
</KeyboardProvider>

src/components/Button/Button.tsx

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,30 @@
1-
/* eslint-disable no-restricted-imports */
21
import { createHitslop } from '@app/utils/string/createHitSlop';
3-
import { forwardRef } from 'react';
4-
import { Pressable, PressableProps, View } from 'react-native';
2+
import { PressableScale, type CustomPressableProps } from 'pressto';
53

6-
export type ButtonProps = PressableProps & {
4+
export type ButtonProps = CustomPressableProps & {
75
label?: string;
6+
disabled?: boolean;
87
};
98

10-
export const Button = forwardRef<View, ButtonProps>(
11-
(
12-
{
13-
children,
14-
onPress,
15-
style,
16-
hitSlop = createHitslop(10),
17-
label,
18-
...touchableProps
19-
},
20-
ref,
21-
) => (
22-
<Pressable
23-
ref={ref}
9+
export function Button({
10+
children,
11+
onPress,
12+
style,
13+
hitSlop = createHitslop(10),
14+
label,
15+
disabled,
16+
...touchableProps
17+
}: ButtonProps) {
18+
return (
19+
<PressableScale
2420
accessibilityLabel={label}
2521
{...touchableProps}
2622
hitSlop={hitSlop}
2723
style={style}
24+
enabled={!disabled}
2825
onPress={onPress}
2926
>
3027
{children}
31-
</Pressable>
32-
),
33-
);
34-
35-
Button.displayName = 'Button';
28+
</PressableScale>
29+
);
30+
}

src/components/Chat/Chat.tsx

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,42 @@ export const Chat = memo(({ channelName, channelId }: ChatProps) => {
139139

140140
const mentionColorCache = useRef<Map<string, string>>(new Map());
141141
const fetchedCosmeticsUsers = useRef<Set<string>>(new Set());
142+
const chatStartTimeRef = useRef<number | null>(null);
142143

143144
useTwitchWs();
144145

146+
useEffect(() => {
147+
chatStartTimeRef.current = Date.now();
148+
}, [channelId]);
149+
150+
const canFetchCosmetics = useCallback((): boolean => {
151+
const chatStartTime = chatStartTimeRef.current;
152+
if (!chatStartTime) {
153+
return true;
154+
}
155+
156+
const elapsedSeconds = (Date.now() - chatStartTime) / 1000;
157+
return elapsedSeconds <= 10;
158+
}, []);
159+
145160
const fetchUserCosmetics = useCallback(
146161
async (twitchUserId: string) => {
147162
if (fetchedCosmeticsUsers.current.has(twitchUserId)) {
148163
return;
149164
}
150165

166+
// Only fetch cosmetics for the first 10 seconds of chat to prevent API overload
167+
if (!canFetchCosmetics()) {
168+
const chatStartTime = chatStartTimeRef.current;
169+
const elapsedSeconds = chatStartTime
170+
? (Date.now() - chatStartTime) / 1000
171+
: 0;
172+
logger.stvWs.debug(
173+
`Skipping cosmetic fetch for ${twitchUserId} - chat has been active for ${elapsedSeconds.toFixed(1)}s (limit: 10s)`,
174+
);
175+
return;
176+
}
177+
151178
// Mark as fetching to prevent duplicate requests
152179
fetchedCosmeticsUsers.current.add(twitchUserId);
153180

@@ -181,7 +208,7 @@ export const Chat = memo(({ channelName, channelId }: ChatProps) => {
181208
);
182209
}
183210
},
184-
[userPaints],
211+
[userPaints, canFetchCosmetics],
185212
);
186213

187214
const {
@@ -490,8 +517,14 @@ export const Chat = memo(({ channelName, channelId }: ChatProps) => {
490517
if (paintId) {
491518
const existingPaint = getPaint(paintId);
492519
if (!existingPaint && sevenTvUserId) {
493-
// Paint not in cache, fetch user's cosmetics via GQL
494-
await fetchAndCacheUserCosmetics(sevenTvUserId);
520+
// Paint not in cache, fetch user's cosmetics via GQL (only if within 10s limit)
521+
if (canFetchCosmetics()) {
522+
await fetchAndCacheUserCosmetics(sevenTvUserId);
523+
} else {
524+
logger.stvWs.debug(
525+
`Skipping cosmetic fetch for entitlement - 10s limit exceeded`,
526+
);
527+
}
495528
} else if (data.ttvUserId) {
496529
// Paint already cached, just link the user
497530
setUserPaint(data.ttvUserId, paintId);
@@ -504,8 +537,14 @@ export const Chat = memo(({ channelName, channelId }: ChatProps) => {
504537
if (badgeId) {
505538
const existingBadge = getBadge(badgeId);
506539
if (!existingBadge && sevenTvUserId) {
507-
// Badge not in cache, fetch user's cosmetics via GQL
508-
await fetchAndCacheUserCosmetics(sevenTvUserId);
540+
// Badge not in cache, fetch user's cosmetics via GQL (only if within 10s limit)
541+
if (canFetchCosmetics()) {
542+
await fetchAndCacheUserCosmetics(sevenTvUserId);
543+
} else {
544+
logger.stvWs.debug(
545+
`Skipping cosmetic fetch for entitlement - 10s limit exceeded`,
546+
);
547+
}
509548
} else if (data.ttvUserId) {
510549
// Badge already cached, just link the user
511550
setUserBadge(data.ttvUserId, badgeId);

src/components/Chat/components/SettingsSheet/SettingsSheet.tsx

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -78,26 +78,14 @@ const SettingsSheetComponent = forwardRef<TrueSheet, SettingsSheetProps>(
7878
</View>
7979

8080
<View style={styles.menuContainer}>
81-
<Button
82-
style={({ pressed }) => [
83-
styles.menuItem,
84-
pressed && styles.menuItemPressed,
85-
]}
86-
onPress={handleRefetchEmotes}
87-
>
81+
<Button style={styles.menuItem} onPress={handleRefetchEmotes}>
8882
<Icon icon="refresh-cw" color={theme.colors.gray.borderHover} />
8983
<Text style={styles.menuItemText} weight="semibold">
9084
Refetch Emotes & Badges
9185
</Text>
9286
</Button>
9387

94-
<Button
95-
style={({ pressed }) => [
96-
styles.menuItem,
97-
pressed && styles.menuItemPressed,
98-
]}
99-
onPress={handleReconnect}
100-
>
88+
<Button style={styles.menuItem} onPress={handleReconnect}>
10189
<Icon icon="wifi" color={theme.colors.gray.borderHover} />
10290
<Text style={styles.menuItemText} weight="semibold">
10391
Reconnect
@@ -118,13 +106,7 @@ const SettingsSheetComponent = forwardRef<TrueSheet, SettingsSheetProps>(
118106
</View>
119107
</View>
120108

121-
<Button
122-
style={({ pressed }) => [
123-
styles.menuItem,
124-
pressed && styles.menuItemPressed,
125-
]}
126-
onPress={handleRefreshVideo}
127-
>
109+
<Button style={styles.menuItem} onPress={handleRefreshVideo}>
128110
<Icon icon="video" color={theme.colors.gray.borderHover} />
129111
<Text style={styles.menuItemText} weight="semibold">
130112
Refresh Video

src/components/NavigationSectionList/NavigationSectionListItemButton.tsx

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import { forwardRef, Ref } from 'react';
2-
3-
import { View } from 'react-native';
41
import { StyleSheet, useUnistyles } from 'react-native-unistyles';
2+
import { View } from 'react-native';
53
import { Button, ButtonProps } from '../Button';
64
import { Icon } from '../Icon';
75
import { Image } from '../Image';
@@ -10,41 +8,37 @@ import { SectionListItem } from './NavigationSectionList';
108

119
type NavigationSectionListItemButtonProps = ButtonProps & SectionListItem;
1210

13-
export const NavigationSectionListItemButton = forwardRef(
14-
(
15-
{
16-
title,
17-
iconName: icon,
18-
picture,
19-
description,
20-
onPress,
21-
...props
22-
}: NavigationSectionListItemButtonProps,
23-
ref: Ref<View>,
24-
) => {
25-
const { theme } = useUnistyles();
11+
export function NavigationSectionListItemButton({
12+
title,
13+
iconName: icon,
14+
picture,
15+
description,
16+
onPress,
17+
...props
18+
}: NavigationSectionListItemButtonProps) {
19+
const { theme } = useUnistyles();
2620

27-
return (
28-
<Button {...props} ref={ref} onPress={e => onPress?.(e)}>
29-
<View style={styles.container}>
30-
<View style={styles.contentWrapper}>
31-
{icon && <Icon icon={icon} size={20} />}
32-
{picture && <Image source={picture} style={styles.image} />}
33-
<View style={styles.textWrapper}>
34-
<Text>{title}</Text>
35-
<Text>{description}</Text>
36-
</View>
21+
return (
22+
<Button {...props} onPress={onPress}>
23+
<View style={styles.container}>
24+
<View style={styles.contentWrapper}>
25+
{icon && <Icon icon={icon} size={20} />}
26+
{picture && <Image source={picture} style={styles.image} />}
27+
<View style={styles.textWrapper}>
28+
<Text>{title}</Text>
29+
<Text>{description}</Text>
3730
</View>
38-
<Icon
39-
icon="arrow-right"
40-
size={20}
41-
color={theme.colors.black.accentHoverAlpha}
42-
/>
4331
</View>
44-
</Button>
45-
);
46-
},
47-
);
32+
<Icon
33+
icon="arrow-right"
34+
size={20}
35+
color={theme.colors.black.accentHoverAlpha}
36+
/>
37+
</View>
38+
</Button>
39+
);
40+
}
41+
4842
NavigationSectionListItemButton.displayName = 'NavigationSectionListItemButton';
4943

5044
const styles = StyleSheet.create(theme => ({

0 commit comments

Comments
 (0)