@squad-sports/react-native 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +97 -0
- package/package.json +46 -0
- package/src/SquadExperience.tsx +200 -0
- package/src/SquadProvider.tsx +232 -0
- package/src/SquadSportsSDK.ts +286 -0
- package/src/__tests__/DeepLinkHandler.test.ts +101 -0
- package/src/__tests__/ErrorBoundary.test.tsx +161 -0
- package/src/__tests__/EventProcessor.test.ts +241 -0
- package/src/__tests__/PushNotificationHandler.test.ts +91 -0
- package/src/__tests__/SecureStorage.test.ts +62 -0
- package/src/__tests__/SquadSportsSDK.test.ts +278 -0
- package/src/__tests__/VerificationCooldown.test.ts +153 -0
- package/src/components/ErrorBoundary.tsx +129 -0
- package/src/components/SOTD/SOTDComponents.tsx +101 -0
- package/src/components/audio/AudioPlayerRow.tsx +189 -0
- package/src/components/audio/recording/AudioRecording.tsx +232 -0
- package/src/components/communities/CommunityComponents.tsx +78 -0
- package/src/components/dialogs/AllDialogs.tsx +123 -0
- package/src/components/dialogs/ConfirmDialog.tsx +77 -0
- package/src/components/dialogs/PermissionDialog.tsx +132 -0
- package/src/components/dragAndDrop/DragAndDrop.tsx +56 -0
- package/src/components/events/EventComponents.tsx +93 -0
- package/src/components/feed/ChatBannerCard.tsx +94 -0
- package/src/components/feed/FeedLoadingSkeleton.tsx +43 -0
- package/src/components/feed/FreestyleCard.tsx +119 -0
- package/src/components/feed/InterstitialOverlay.tsx +190 -0
- package/src/components/feed/PollCard.tsx +158 -0
- package/src/components/feed/SponsoredContentCard.tsx +118 -0
- package/src/components/freestyle/FreestyleComponents.tsx +148 -0
- package/src/components/index.ts +42 -0
- package/src/components/message/MessageCard.tsx +166 -0
- package/src/components/message/MessageComponents.tsx +143 -0
- package/src/components/poll/PollComponents.tsx +226 -0
- package/src/components/sentinels/Sentinels.tsx +175 -0
- package/src/components/squad/FeedSquad.tsx +54 -0
- package/src/components/toasts/Toasts.tsx +88 -0
- package/src/components/ux/RootUXComponents.tsx +157 -0
- package/src/components/ux/action-sheet/ActionSheet.tsx +67 -0
- package/src/components/ux/bottom-sheet/BottomSheetComponents.tsx +93 -0
- package/src/components/ux/bottom-sheet/SquadBottomSheet.tsx +119 -0
- package/src/components/ux/buttons/Button.tsx +37 -0
- package/src/components/ux/buttons/InfoButton.tsx +24 -0
- package/src/components/ux/buttons/XButton.tsx +27 -0
- package/src/components/ux/carousel/Carousel.tsx +134 -0
- package/src/components/ux/emoji-picker/EmojiReactionPicker.tsx +139 -0
- package/src/components/ux/errors/ErrorHint.tsx +46 -0
- package/src/components/ux/inputs/CodeInput.tsx +121 -0
- package/src/components/ux/inputs/DatePicker.tsx +76 -0
- package/src/components/ux/inputs/MaskedTextInput.tsx +95 -0
- package/src/components/ux/inputs/PhoneNumberInput.tsx +90 -0
- package/src/components/ux/inputs/SearchTextInput.tsx +70 -0
- package/src/components/ux/inputs/TextInput.tsx +58 -0
- package/src/components/ux/layout/AvoidKeyboardScreen.tsx +58 -0
- package/src/components/ux/layout/BlurOverlay.tsx +26 -0
- package/src/components/ux/layout/CrossFade.tsx +30 -0
- package/src/components/ux/layout/DismissKeyboardOnBlur.tsx +14 -0
- package/src/components/ux/layout/LoadingOverlay.tsx +60 -0
- package/src/components/ux/layout/NetworkBanner.tsx +64 -0
- package/src/components/ux/layout/PermissionsCTAContent.tsx +54 -0
- package/src/components/ux/layout/RefreshControl.tsx +7 -0
- package/src/components/ux/layout/Screen.tsx +31 -0
- package/src/components/ux/layout/ScreenHeader.tsx +89 -0
- package/src/components/ux/layout/TabBar.tsx +39 -0
- package/src/components/ux/layout/Toast.tsx +116 -0
- package/src/components/ux/layout/TransparentOverlay.tsx +21 -0
- package/src/components/ux/navigation/BackButton.tsx +29 -0
- package/src/components/ux/navigation/LinkButton.tsx +21 -0
- package/src/components/ux/navigation/UrlButton.tsx +25 -0
- package/src/components/ux/scroll/RefreshableFlatList.tsx +35 -0
- package/src/components/ux/shapes/Shapes.tsx +23 -0
- package/src/components/ux/text/Typography.tsx +28 -0
- package/src/components/ux/user-image/UserImage.tsx +75 -0
- package/src/components/ux/user-image/UserImageVariants.tsx +104 -0
- package/src/components/wallet/WalletComponents.tsx +116 -0
- package/src/contexts/AuthContext.tsx +45 -0
- package/src/contexts/EventProcessorContext.tsx +41 -0
- package/src/contexts/PlayerQueueContext.tsx +95 -0
- package/src/hooks/useAuth.ts +23 -0
- package/src/hooks/useDataRefresh.ts +30 -0
- package/src/hooks/useEventProcessor.ts +6 -0
- package/src/hooks/useImageOptimization.ts +59 -0
- package/src/hooks/useOnboardingStepGuard.ts +36 -0
- package/src/hooks/usePendingNavigation.ts +26 -0
- package/src/hooks/useSquadData.ts +84 -0
- package/src/hooks/useUserCreated.ts +25 -0
- package/src/hooks/useUserUpdate.ts +25 -0
- package/src/hooks/useViewabilityTracker.ts +40 -0
- package/src/index.ts +109 -0
- package/src/navigation/SquadNavigator.tsx +262 -0
- package/src/realtime/DeepLinkHandler.ts +113 -0
- package/src/realtime/EventProcessor.ts +313 -0
- package/src/realtime/NetworkMonitor.ts +84 -0
- package/src/realtime/OfflineQueue.ts +133 -0
- package/src/realtime/PushNotificationHandler.ts +125 -0
- package/src/realtime/useRealtimeSync.ts +84 -0
- package/src/screens/auth/EmailVerificationScreen.tsx +201 -0
- package/src/screens/auth/EnterCodeScreen.tsx +253 -0
- package/src/screens/auth/EnterEmailScreen.tsx +234 -0
- package/src/screens/auth/LandingScreen.tsx +90 -0
- package/src/screens/auth/LoginScreen.tsx +126 -0
- package/src/screens/events/EventScreen.tsx +163 -0
- package/src/screens/freestyle/CommunityFreestyleScreen.tsx +82 -0
- package/src/screens/freestyle/FreestyleCreationScreen.tsx +255 -0
- package/src/screens/freestyle/FreestyleListensScreen.tsx +44 -0
- package/src/screens/freestyle/FreestyleReactionsScreen.tsx +44 -0
- package/src/screens/freestyle/FreestyleScreen.tsx +64 -0
- package/src/screens/home/HomeScreen.tsx +365 -0
- package/src/screens/home/slivers/SquadCircle.tsx +77 -0
- package/src/screens/invite/InvitationQrCodeScreen.tsx +114 -0
- package/src/screens/invite/InviteScreen.tsx +175 -0
- package/src/screens/messaging/MessagingScreen.tsx +360 -0
- package/src/screens/onboarding/OnboardingAccountSetupScreen.tsx +221 -0
- package/src/screens/onboarding/OnboardingTeamSelectScreen.tsx +215 -0
- package/src/screens/onboarding/OnboardingWelcomeScreen.tsx +93 -0
- package/src/screens/polls/PollResponseScreen.tsx +229 -0
- package/src/screens/polls/PollSummationScreen.tsx +78 -0
- package/src/screens/profile/ProfileScreen.tsx +234 -0
- package/src/screens/settings/BlockedUsersScreen.tsx +139 -0
- package/src/screens/settings/DeleteAccountScreen.tsx +182 -0
- package/src/screens/settings/EditProfileScreen.tsx +154 -0
- package/src/screens/settings/NetworkStatusScreen.tsx +59 -0
- package/src/screens/settings/SettingsScreen.tsx +194 -0
- package/src/screens/squad-line/AddCallTitleScreen.tsx +293 -0
- package/src/screens/wallet/WalletScreen.tsx +174 -0
- package/src/services/AuthStateManager.ts +93 -0
- package/src/services/NavigationService.ts +40 -0
- package/src/services/UserDataManager.ts +59 -0
- package/src/services/UserUpdateService.ts +31 -0
- package/src/services/VerificationStateManager.ts +41 -0
- package/src/squad-line/CallScreen.tsx +158 -0
- package/src/squad-line/IncomingCallOverlay.tsx +113 -0
- package/src/squad-line/SquadLineClient.ts +327 -0
- package/src/squad-line/useSquadLine.ts +80 -0
- package/src/state/audio.ts +38 -0
- package/src/state/client.ts +26 -0
- package/src/state/communities.ts +45 -0
- package/src/state/contacts.ts +28 -0
- package/src/state/device-info.ts +22 -0
- package/src/state/events.ts +16 -0
- package/src/state/features.ts +57 -0
- package/src/state/index.ts +121 -0
- package/src/state/invitations.ts +16 -0
- package/src/state/modal-keys.ts +63 -0
- package/src/state/modal-queue.ts +104 -0
- package/src/state/navigation.ts +34 -0
- package/src/state/permissions.ts +43 -0
- package/src/state/session.ts +223 -0
- package/src/state/squaddie-of-the-day.ts +21 -0
- package/src/state/sync/crdt.ts +70 -0
- package/src/state/sync/dependable.ts +213 -0
- package/src/state/sync/feed-v2.ts +42 -0
- package/src/state/sync/messages.ts +44 -0
- package/src/state/sync/offline-support.ts +42 -0
- package/src/state/sync/polls.ts +37 -0
- package/src/state/sync/refresh.ts +24 -0
- package/src/state/sync/squad-v2.ts +25 -0
- package/src/state/ui.ts +36 -0
- package/src/state/user.ts +46 -0
- package/src/state/wallet.ts +26 -0
- package/src/storage/SecureStorage.ts +77 -0
- package/src/theme/ThemeContext.tsx +159 -0
- package/src/types/modules.d.ts +165 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { useApiClient } from '../SquadProvider';
|
|
3
|
+
import { SquadLineClient, type CallState, type CallInfo } from './SquadLineClient';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* React hook for Squad Line voice calling.
|
|
7
|
+
* Wraps SquadLineClient for easy consumption in components.
|
|
8
|
+
*/
|
|
9
|
+
export function useSquadLine() {
|
|
10
|
+
const apiClient = useApiClient();
|
|
11
|
+
const [callState, setCallState] = useState<CallState>('idle');
|
|
12
|
+
const [currentCall, setCurrentCall] = useState<CallInfo | null>(null);
|
|
13
|
+
const [isMuted, setIsMuted] = useState(false);
|
|
14
|
+
const [isSpeakerOn, setIsSpeakerOn] = useState(false);
|
|
15
|
+
const [incomingCall, setIncomingCall] = useState<CallInfo | null>(null);
|
|
16
|
+
|
|
17
|
+
const client = SquadLineClient.getInstance(apiClient);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const onCallStateChanged = (state: CallState) => setCallState(state);
|
|
21
|
+
const onMuteChanged = (muted: boolean) => setIsMuted(muted);
|
|
22
|
+
const onSpeakerChanged = (speaker: boolean) => setIsSpeakerOn(speaker);
|
|
23
|
+
const onIncomingCall = (info: CallInfo) => setIncomingCall(info);
|
|
24
|
+
const onConnected = (info: CallInfo) => {
|
|
25
|
+
setCurrentCall(info);
|
|
26
|
+
setIncomingCall(null);
|
|
27
|
+
};
|
|
28
|
+
const onDisconnected = () => {
|
|
29
|
+
setCurrentCall(null);
|
|
30
|
+
setIncomingCall(null);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
client.on('callStateChanged', onCallStateChanged);
|
|
34
|
+
client.on('muteChanged', onMuteChanged);
|
|
35
|
+
client.on('speakerChanged', onSpeakerChanged);
|
|
36
|
+
client.on('incomingCall', onIncomingCall);
|
|
37
|
+
client.on('connected', onConnected);
|
|
38
|
+
client.on('disconnected', onDisconnected);
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
client.off('callStateChanged', onCallStateChanged);
|
|
42
|
+
client.off('muteChanged', onMuteChanged);
|
|
43
|
+
client.off('speakerChanged', onSpeakerChanged);
|
|
44
|
+
client.off('incomingCall', onIncomingCall);
|
|
45
|
+
client.off('connected', onConnected);
|
|
46
|
+
client.off('disconnected', onDisconnected);
|
|
47
|
+
};
|
|
48
|
+
}, [client]);
|
|
49
|
+
|
|
50
|
+
const makeCall = useCallback(
|
|
51
|
+
(connectionId: string, title: string, calleeIdentity?: string) =>
|
|
52
|
+
client.makeCall(connectionId, title, calleeIdentity),
|
|
53
|
+
[client],
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const endCall = useCallback(() => client.endCall(), [client]);
|
|
57
|
+
const acceptCall = useCallback(() => client.acceptCall(), [client]);
|
|
58
|
+
const rejectCall = useCallback(() => client.rejectCall(), [client]);
|
|
59
|
+
const toggleMute = useCallback(() => client.toggleMute(), [client]);
|
|
60
|
+
const toggleSpeaker = useCallback(() => client.toggleSpeaker(), [client]);
|
|
61
|
+
const sendReaction = useCallback(
|
|
62
|
+
(emoji: string, imageUrl?: string) => client.sendReaction(emoji, imageUrl),
|
|
63
|
+
[client],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
callState,
|
|
68
|
+
currentCall,
|
|
69
|
+
incomingCall,
|
|
70
|
+
isMuted,
|
|
71
|
+
isSpeakerOn,
|
|
72
|
+
makeCall,
|
|
73
|
+
endCall,
|
|
74
|
+
acceptCall,
|
|
75
|
+
rejectCall,
|
|
76
|
+
toggleMute,
|
|
77
|
+
toggleSpeaker,
|
|
78
|
+
sendReaction,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio playback/recording atoms.
|
|
3
|
+
* Ported from squad-demo/src/atoms/audio.ts.
|
|
4
|
+
*/
|
|
5
|
+
import { atom, atomFamily } from 'recoil';
|
|
6
|
+
|
|
7
|
+
export type AudioStatus = {
|
|
8
|
+
isPlaying: boolean;
|
|
9
|
+
isLoaded: boolean;
|
|
10
|
+
durationMs: number;
|
|
11
|
+
positionMs: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const playerForRecorder = atomFamily<unknown | null, string>({
|
|
15
|
+
key: 'squad-sdk:audio:recorder:player',
|
|
16
|
+
default: null,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const statusForAudioDevice = atomFamily<AudioStatus | null, string | null>({
|
|
20
|
+
key: 'squad-sdk:audio:device:status',
|
|
21
|
+
default: null,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const positionForAudioPlayer = atomFamily<number, string | null>({
|
|
25
|
+
key: 'squad-sdk:audio:device:progress',
|
|
26
|
+
default: 0,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Global audio state
|
|
30
|
+
export const isAudioRecording = atom<boolean>({
|
|
31
|
+
key: 'squad-sdk:audio:isRecording',
|
|
32
|
+
default: false,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const activeAudioPlayerId = atom<string | null>({
|
|
36
|
+
key: 'squad-sdk:audio:activePlayerId',
|
|
37
|
+
default: null,
|
|
38
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API client selectors.
|
|
3
|
+
* Ported from squad-demo/src/atoms/client.ts.
|
|
4
|
+
*/
|
|
5
|
+
import { selector } from 'recoil';
|
|
6
|
+
import { reActiveAccessToken } from './session';
|
|
7
|
+
import { configApp } from './features';
|
|
8
|
+
|
|
9
|
+
// Preferred API environment
|
|
10
|
+
export const rePreferredApiEnvironment = selector<string>({
|
|
11
|
+
key: 'squad-sdk:client:preferredApiEnvironment',
|
|
12
|
+
get: ({ get }) => {
|
|
13
|
+
const config = get(configApp);
|
|
14
|
+
return config.apiEnvironments[0]?.squadApiBaseUrl ?? 'https://api-release.withyoursquad.com';
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Client selector that creates API client from current token + environment
|
|
19
|
+
export const reAnyClient = selector<{ baseUrl: string; accessToken: string | null }>({
|
|
20
|
+
key: 'squad-sdk:client:any',
|
|
21
|
+
get: ({ get }) => {
|
|
22
|
+
const accessToken = get(reActiveAccessToken);
|
|
23
|
+
const baseUrl = get(rePreferredApiEnvironment);
|
|
24
|
+
return { baseUrl, accessToken };
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Community atoms.
|
|
3
|
+
* Ported from squad-demo/src/atoms/communities.ts + community-reactions.ts + community-selection.ts + onboarding-community.ts.
|
|
4
|
+
*/
|
|
5
|
+
import { atom, selector, selectorFamily } from 'recoil';
|
|
6
|
+
import type { Community, Communities } from '@squad-sports/core';
|
|
7
|
+
|
|
8
|
+
export const reAllCommunities = atom<Community[]>({
|
|
9
|
+
key: 'squad-sdk:communities:all',
|
|
10
|
+
default: [],
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export const reAllCommunitiesByGroups = atom<unknown | undefined>({
|
|
14
|
+
key: 'squad-sdk:communities:byGroup',
|
|
15
|
+
default: undefined,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const reCommunityById = selectorFamily<Community | null, string>({
|
|
19
|
+
key: 'squad-sdk:communities:byId',
|
|
20
|
+
get: (id) => ({ get }) => {
|
|
21
|
+
return get(reAllCommunities).find(c => c.id === id) ?? null;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const reCommunitiesRefresh = atom<number>({
|
|
26
|
+
key: 'squad-sdk:communities:refresh',
|
|
27
|
+
default: 0,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const reUserCommunity = atom<Community | null>({
|
|
31
|
+
key: 'squad-sdk:communities:userCommunity',
|
|
32
|
+
default: null,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Community reactions
|
|
36
|
+
export const reCommunityReactions = atom<Map<string, unknown[]>>({
|
|
37
|
+
key: 'squad-sdk:communities:reactions',
|
|
38
|
+
default: new Map(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Onboarding community selection
|
|
42
|
+
export const reOnboardingSelectedCommunity = atom<Community | null>({
|
|
43
|
+
key: 'squad-sdk:onboarding:selectedCommunity',
|
|
44
|
+
default: null,
|
|
45
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device contacts atoms.
|
|
3
|
+
* Ported from squad-demo/src/atoms/contacts.ts.
|
|
4
|
+
*/
|
|
5
|
+
import { atom } from 'recoil';
|
|
6
|
+
|
|
7
|
+
export interface DeviceContact {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
phoneNumbers: string[];
|
|
11
|
+
emails: string[];
|
|
12
|
+
imageUri?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const deviceContacts = atom<DeviceContact[]>({
|
|
16
|
+
key: 'squad-sdk:contacts:device',
|
|
17
|
+
default: [],
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const contactsLoaded = atom<boolean>({
|
|
21
|
+
key: 'squad-sdk:contacts:loaded',
|
|
22
|
+
default: false,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const contactsOnSquad = atom<string[]>({
|
|
26
|
+
key: 'squad-sdk:contacts:onSquad',
|
|
27
|
+
default: [],
|
|
28
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device info atoms.
|
|
3
|
+
* Ported from squad-demo/src/atoms/device-info.ts.
|
|
4
|
+
*/
|
|
5
|
+
import { atom } from 'recoil';
|
|
6
|
+
|
|
7
|
+
export const reDeviceInfo = atom<{
|
|
8
|
+
platform: string;
|
|
9
|
+
osVersion: string;
|
|
10
|
+
appVersion: string;
|
|
11
|
+
deviceModel: string;
|
|
12
|
+
pushToken: string | null;
|
|
13
|
+
}>({
|
|
14
|
+
key: 'squad-sdk:device:info',
|
|
15
|
+
default: {
|
|
16
|
+
platform: '',
|
|
17
|
+
osVersion: '',
|
|
18
|
+
appVersion: '',
|
|
19
|
+
deviceModel: '',
|
|
20
|
+
pushToken: null,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event atoms.
|
|
3
|
+
* Ported from squad-demo/src/atoms/events.ts.
|
|
4
|
+
*/
|
|
5
|
+
import { atom } from 'recoil';
|
|
6
|
+
import type { Events } from '@squad-sports/core';
|
|
7
|
+
|
|
8
|
+
export const reActiveEvents = atom<Events | null>({
|
|
9
|
+
key: 'squad-sdk:events:active',
|
|
10
|
+
default: null,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export const reEventsRefresh = atom<number>({
|
|
14
|
+
key: 'squad-sdk:events:refresh',
|
|
15
|
+
default: 0,
|
|
16
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature flag and config atoms.
|
|
3
|
+
* Ported from squad-demo/src/atoms/features.ts + config-app.ts + config-squad.ts + developer-access.ts + app-store.ts.
|
|
4
|
+
*/
|
|
5
|
+
import { atom, selector } from 'recoil';
|
|
6
|
+
|
|
7
|
+
// Feature flags
|
|
8
|
+
export const featureFlags = atom<Record<string, boolean>>({
|
|
9
|
+
key: 'squad-sdk:features:flags',
|
|
10
|
+
default: {
|
|
11
|
+
squadLine: true,
|
|
12
|
+
freestyle: true,
|
|
13
|
+
messaging: true,
|
|
14
|
+
polls: true,
|
|
15
|
+
events: true,
|
|
16
|
+
wallet: true,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// App config
|
|
21
|
+
export interface ApiEnvironment {
|
|
22
|
+
squadApiBaseUrl: string;
|
|
23
|
+
name: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const configApp = atom<{
|
|
27
|
+
apiEnvironments: ApiEnvironment[];
|
|
28
|
+
}>({
|
|
29
|
+
key: 'squad-sdk:config:app',
|
|
30
|
+
default: {
|
|
31
|
+
apiEnvironments: [{ squadApiBaseUrl: 'https://api-release.withyoursquad.com', name: 'production' }],
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Developer access
|
|
36
|
+
export const developerAccessEnabled = atom<boolean>({
|
|
37
|
+
key: 'squad-sdk:developer:access',
|
|
38
|
+
default: false,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// App store version
|
|
42
|
+
export const reLatestAppStoreVersion = atom<string | null>({
|
|
43
|
+
key: 'squad-sdk:appStore:latestVersion',
|
|
44
|
+
default: null,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Session count
|
|
48
|
+
export const sessionCount = atom<number>({
|
|
49
|
+
key: 'squad-sdk:session:count',
|
|
50
|
+
default: 0,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Prerelease
|
|
54
|
+
export const rePrereleaseEnabled = atom<boolean>({
|
|
55
|
+
key: 'squad-sdk:features:prerelease',
|
|
56
|
+
default: false,
|
|
57
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State management — all Recoil atoms.
|
|
3
|
+
* Complete port from squad-demo/src/atoms/.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// CRDT
|
|
7
|
+
export { compileCrdt, compileCrdtSingle } from './sync/crdt';
|
|
8
|
+
export type { CrdtArray, CrdtArrayItem, CrdtArrayOperation } from './sync/crdt';
|
|
9
|
+
|
|
10
|
+
// Dependable
|
|
11
|
+
export { DependableAtom, DependableSelector, DependableAtomFamily } from './sync/dependable';
|
|
12
|
+
|
|
13
|
+
// Refresh
|
|
14
|
+
export { reLastRefreshed, reRefresh, reRefreshing } from './sync/refresh';
|
|
15
|
+
|
|
16
|
+
// Offline
|
|
17
|
+
export { isOnlineAtom, pendingActionsAtom, offlineCacheAtom, shouldUseOfflineDataSelector } from './sync/offline-support';
|
|
18
|
+
export type { QueuedOfflineAction, OfflineActionType } from './sync/offline-support';
|
|
19
|
+
|
|
20
|
+
// Session / Auth
|
|
21
|
+
export {
|
|
22
|
+
reActivePhoneNumber, reActiveEmail, reActiveCode,
|
|
23
|
+
reActiveAccessToken, reActiveUserId, reUserRefresh,
|
|
24
|
+
reAuthHydrated, rePendingNavigation, reAttemptedVerifications,
|
|
25
|
+
pruneExpiredAttemptedVerifications, ATTEMPTED_VERIFICATION_WINDOW_MS,
|
|
26
|
+
} from './session';
|
|
27
|
+
export type { AttemptedVerification } from './session';
|
|
28
|
+
|
|
29
|
+
// Permissions
|
|
30
|
+
export {
|
|
31
|
+
microphonePermissions, contactsPermissions, cameraPermissions,
|
|
32
|
+
imagesPermissions, notificationsPermissions,
|
|
33
|
+
} from './permissions';
|
|
34
|
+
export type { PermissionStatus } from './permissions';
|
|
35
|
+
|
|
36
|
+
// Modal queue
|
|
37
|
+
export { default as modalQueue, ModalQueueItem, ModalType, topModalAtom } from './modal-queue';
|
|
38
|
+
export { DialogKey, ToastKey, BottomSheetKey } from './modal-keys';
|
|
39
|
+
export type { ModalKey, DialogProps, ToastProps, BottomSheetProps } from './modal-keys';
|
|
40
|
+
export type { ModalQueueOptions } from './modal-queue';
|
|
41
|
+
|
|
42
|
+
// Communities
|
|
43
|
+
export {
|
|
44
|
+
reAllCommunities, reAllCommunitiesByGroups, reCommunityById,
|
|
45
|
+
reCommunitiesRefresh, reUserCommunity, reCommunityReactions,
|
|
46
|
+
reOnboardingSelectedCommunity,
|
|
47
|
+
} from './communities';
|
|
48
|
+
|
|
49
|
+
// Navigation
|
|
50
|
+
export { currentStack, navigationState, reOnboardingStep, rePostOnboardingLoadingActive } from './navigation';
|
|
51
|
+
export type { ScreenName, OnboardingStep } from './navigation';
|
|
52
|
+
|
|
53
|
+
// Audio
|
|
54
|
+
export {
|
|
55
|
+
playerForRecorder, statusForAudioDevice, positionForAudioPlayer,
|
|
56
|
+
isAudioRecording, activeAudioPlayerId,
|
|
57
|
+
} from './audio';
|
|
58
|
+
export type { AudioStatus } from './audio';
|
|
59
|
+
|
|
60
|
+
// UI
|
|
61
|
+
export {
|
|
62
|
+
loadingOverlayVisible, loadingOverlayText,
|
|
63
|
+
aKeyboardOpen, networkBannerVisible, networkStateAtom,
|
|
64
|
+
} from './ui';
|
|
65
|
+
|
|
66
|
+
// Features / Config
|
|
67
|
+
export {
|
|
68
|
+
featureFlags, configApp, developerAccessEnabled,
|
|
69
|
+
reLatestAppStoreVersion, sessionCount, rePrereleaseEnabled,
|
|
70
|
+
} from './features';
|
|
71
|
+
export type { ApiEnvironment } from './features';
|
|
72
|
+
|
|
73
|
+
// Contacts
|
|
74
|
+
export { deviceContacts, contactsLoaded, contactsOnSquad } from './contacts';
|
|
75
|
+
export type { DeviceContact } from './contacts';
|
|
76
|
+
|
|
77
|
+
// Wallet
|
|
78
|
+
export { reWallet, reCoupons, reBrands, reWalletRefresh } from './wallet';
|
|
79
|
+
|
|
80
|
+
// SOTD
|
|
81
|
+
export { reSquaddieOfTheDay, reSOTDAnimationShown, reSOTDIntroShown } from './squaddie-of-the-day';
|
|
82
|
+
|
|
83
|
+
// User
|
|
84
|
+
export {
|
|
85
|
+
reUserCache, reLoggedInUserLoaded, userUpdates,
|
|
86
|
+
reUser, reUserTag, reSophiaUser,
|
|
87
|
+
} from './user';
|
|
88
|
+
|
|
89
|
+
// Client
|
|
90
|
+
export { rePreferredApiEnvironment, reAnyClient } from './client';
|
|
91
|
+
|
|
92
|
+
// Sync — Squad
|
|
93
|
+
export { reInitialConnections, reConnectionUpdates, reConnectionWithUser } from './sync/squad-v2';
|
|
94
|
+
|
|
95
|
+
// Sync — Feed
|
|
96
|
+
export {
|
|
97
|
+
reInitialFeed, feedUpdates, feedExpiryTickAtom,
|
|
98
|
+
freestyleReactionUpdates, reFreestylePrompts, reFreestyleCreating,
|
|
99
|
+
} from './sync/feed-v2';
|
|
100
|
+
|
|
101
|
+
// Sync — Messages
|
|
102
|
+
export {
|
|
103
|
+
reInitialConversationState, reConnectionMessages, reMessageReaction,
|
|
104
|
+
reMessageSendStatus, reMessagePrompts,
|
|
105
|
+
} from './sync/messages';
|
|
106
|
+
export type { FailedMessageInfo } from './sync/messages';
|
|
107
|
+
|
|
108
|
+
// Sync — Polls
|
|
109
|
+
export {
|
|
110
|
+
reInitialPollFeed, rePollUpdates, rePollResponseUpdates,
|
|
111
|
+
rePollResponseReactionsUpdates, rePollNudgesUpdates,
|
|
112
|
+
} from './sync/polls';
|
|
113
|
+
|
|
114
|
+
// Events
|
|
115
|
+
export { reActiveEvents, reEventsRefresh } from './events';
|
|
116
|
+
|
|
117
|
+
// Invitations
|
|
118
|
+
export { reInvitationCode, reInvitationCodeLoaded } from './invitations';
|
|
119
|
+
|
|
120
|
+
// Device info
|
|
121
|
+
export { reDeviceInfo } from './device-info';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Invitation atoms.
|
|
3
|
+
* Ported from squad-demo/src/atoms/invitations.ts + live/invitation-code.ts.
|
|
4
|
+
*/
|
|
5
|
+
import { atom } from 'recoil';
|
|
6
|
+
import type { InvitationCode } from '@squad-sports/core';
|
|
7
|
+
|
|
8
|
+
export const reInvitationCode = atom<InvitationCode | null>({
|
|
9
|
+
key: 'squad-sdk:invitations:code',
|
|
10
|
+
default: null,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export const reInvitationCodeLoaded = atom<boolean>({
|
|
14
|
+
key: 'squad-sdk:invitations:loaded',
|
|
15
|
+
default: false,
|
|
16
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modal key definitions for dialogs, toasts, and bottom sheets.
|
|
3
|
+
* Ported from squad-demo/src/atoms/modal-keys.ts.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export enum DialogKey {
|
|
7
|
+
blockConfirmation = 'block-confirmation',
|
|
8
|
+
deleteConfirmation = 'delete-confirmation',
|
|
9
|
+
flagConfirmation = 'flag-confirmation',
|
|
10
|
+
micPermissions = 'mic-permissions',
|
|
11
|
+
cameraPermissions = 'camera-permissions',
|
|
12
|
+
imagesPermissions = 'images-permissions',
|
|
13
|
+
notificationPermissions = 'notification-permissions',
|
|
14
|
+
unblockConfirmation = 'unblock-confirmation',
|
|
15
|
+
SOTDIntroduction = 'sotd-introduction',
|
|
16
|
+
SOTDBlocked = 'sotd-blocked',
|
|
17
|
+
progressCongratulation = 'progress-congratulation',
|
|
18
|
+
collectEmail = 'collect-email',
|
|
19
|
+
noConnection = 'no-connection',
|
|
20
|
+
removeFromSquad = 'remove-from-squad',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export enum ToastKey {
|
|
24
|
+
success = 'success',
|
|
25
|
+
error = 'error',
|
|
26
|
+
info = 'info',
|
|
27
|
+
busy = 'busy',
|
|
28
|
+
networkBanner = 'network-banner',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export enum BottomSheetKey {
|
|
32
|
+
squadLineInvitation = 'squad-line-invitation',
|
|
33
|
+
help = 'help',
|
|
34
|
+
quickTip = 'quick-tip',
|
|
35
|
+
sophiaIntroduction = 'sophia-introduction',
|
|
36
|
+
SOTD = 'SOTD',
|
|
37
|
+
upgradeHardLock = 'upgrade-hard-lock',
|
|
38
|
+
upgradeSoftLock = 'upgrade-soft-lock',
|
|
39
|
+
addFreestyleReaction = 'add-freestyle-reaction',
|
|
40
|
+
selectCommunity = 'select-community',
|
|
41
|
+
removeOrBlock = 'remove-or-block',
|
|
42
|
+
addPollReaction = 'add-poll-reaction',
|
|
43
|
+
filterPollSummation = 'filter-poll-summation',
|
|
44
|
+
pollReactions = 'poll-reactions',
|
|
45
|
+
inviteFeatureIntroduction = 'invite-feature-introduction',
|
|
46
|
+
sotdSelectingFriend = 'SOTD-selecting-friend',
|
|
47
|
+
errorInfo = 'error-info',
|
|
48
|
+
inviteContact = 'invite-contact',
|
|
49
|
+
inviteErrorSquadMaxed = 'invite-error-squad-maxed',
|
|
50
|
+
inviteErrorOtherSquadMaxed = 'invite-error-other-squad-maxed',
|
|
51
|
+
requestItemMenu = 'request-item-menu',
|
|
52
|
+
sotdIntroduction = 'sotd-introduction-selecting',
|
|
53
|
+
selectTeam = 'select-team',
|
|
54
|
+
viewAllAttendees = 'events-view-all-attendees',
|
|
55
|
+
redeemWalletCode = 'redeem-wallet-code',
|
|
56
|
+
couponShareSheet = 'coupon-share-sheet',
|
|
57
|
+
couponInternalShareSheet = 'coupon-internal-share-sheet',
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type DialogProps = Record<string, any>;
|
|
61
|
+
export type ToastProps = Record<string, any>;
|
|
62
|
+
export type BottomSheetProps = Record<string, any> & { onDismiss?: () => void };
|
|
63
|
+
export type ModalKey = DialogKey | ToastKey | BottomSheetKey;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modal queue system for managing dialog/toast/bottom sheet display.
|
|
3
|
+
* Ported from squad-demo/src/atoms/modal-queue.ts.
|
|
4
|
+
*/
|
|
5
|
+
import EventEmitter from 'eventemitter3';
|
|
6
|
+
import { atomFamily } from 'recoil';
|
|
7
|
+
import { type ModalKey, type DialogProps, DialogKey, ToastKey, BottomSheetKey } from './modal-keys';
|
|
8
|
+
|
|
9
|
+
export const enum ModalType {
|
|
10
|
+
Dialog = 'dialog',
|
|
11
|
+
Toast = 'toast',
|
|
12
|
+
BottomSheet = 'bottomSheet',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type ModalQueueOptions = Partial<{
|
|
16
|
+
animationType: 'none' | 'slide' | 'fade';
|
|
17
|
+
dismissable: boolean;
|
|
18
|
+
}>;
|
|
19
|
+
|
|
20
|
+
export class ModalQueueItem {
|
|
21
|
+
key: ModalKey;
|
|
22
|
+
keyType: ModalType;
|
|
23
|
+
options: ModalQueueOptions;
|
|
24
|
+
private props?: DialogProps;
|
|
25
|
+
|
|
26
|
+
constructor(key: ModalKey, props?: DialogProps, options: ModalQueueOptions = {}) {
|
|
27
|
+
this.key = key;
|
|
28
|
+
this.options = options;
|
|
29
|
+
this.props = props;
|
|
30
|
+
this.keyType = ModalQueueItem.getTypeForKey(key);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get isDismissable(): boolean {
|
|
34
|
+
if (typeof this.options.dismissable === 'boolean') return this.options.dismissable;
|
|
35
|
+
return this.keyType === ModalType.BottomSheet;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getProps<T extends DialogProps>(): T {
|
|
39
|
+
return (this.props ?? {}) as T;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static getTypeForKey(key: ModalKey): ModalType {
|
|
43
|
+
if (Object.values(DialogKey).includes(key as DialogKey)) return ModalType.Dialog;
|
|
44
|
+
if (Object.values(ToastKey).includes(key as ToastKey)) return ModalType.Toast;
|
|
45
|
+
return ModalType.BottomSheet;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class ModalQueue {
|
|
50
|
+
private queue: ModalQueueItem[] = [];
|
|
51
|
+
private emitter = new EventEmitter();
|
|
52
|
+
|
|
53
|
+
push(key: ModalKey, props?: DialogProps, options?: ModalQueueOptions): void {
|
|
54
|
+
const item = new ModalQueueItem(key, props, options);
|
|
55
|
+
this.queue.push(item);
|
|
56
|
+
this.emitter.emit('change', this.queue);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
pop(): ModalQueueItem | undefined {
|
|
60
|
+
const item = this.queue.shift();
|
|
61
|
+
this.emitter.emit('change', this.queue);
|
|
62
|
+
return item;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
peek(): ModalQueueItem | undefined {
|
|
66
|
+
return this.queue[0];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
dismiss(key?: ModalKey): void {
|
|
70
|
+
if (key) {
|
|
71
|
+
this.queue = this.queue.filter(item => item.key !== key);
|
|
72
|
+
} else {
|
|
73
|
+
this.queue.shift();
|
|
74
|
+
}
|
|
75
|
+
this.emitter.emit('change', this.queue);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
clear(): void {
|
|
79
|
+
this.queue = [];
|
|
80
|
+
this.emitter.emit('change', this.queue);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get length(): number {
|
|
84
|
+
return this.queue.length;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get isEmpty(): boolean {
|
|
88
|
+
return this.queue.length === 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
onChange(callback: (queue: ModalQueueItem[]) => void): () => void {
|
|
92
|
+
this.emitter.on('change', callback);
|
|
93
|
+
return () => this.emitter.off('change', callback);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const modalQueue = new ModalQueue();
|
|
98
|
+
export default modalQueue;
|
|
99
|
+
|
|
100
|
+
// Recoil atom for top modal (used by renderers)
|
|
101
|
+
export const topModalAtom = atomFamily<ModalQueueItem | null, string>({
|
|
102
|
+
key: 'squad-sdk:modal:top',
|
|
103
|
+
default: null,
|
|
104
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navigation atoms.
|
|
3
|
+
* Ported from squad-demo/src/atoms/navigation.ts + onboarding.ts + post-onboarding.ts.
|
|
4
|
+
*/
|
|
5
|
+
import { atom } from 'recoil';
|
|
6
|
+
|
|
7
|
+
export type ScreenName = string;
|
|
8
|
+
|
|
9
|
+
export const currentStack = atom<ScreenName[]>({
|
|
10
|
+
key: 'squad-sdk:navigation:currentStack',
|
|
11
|
+
default: [],
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const navigationState = atom<'auth' | 'onboarding' | 'main'>({
|
|
15
|
+
key: 'squad-sdk:navigation:state',
|
|
16
|
+
default: 'auth',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Onboarding step tracking
|
|
20
|
+
export type OnboardingStep =
|
|
21
|
+
| 'welcome'
|
|
22
|
+
| 'teamSelect'
|
|
23
|
+
| 'accountSetup'
|
|
24
|
+
| 'complete';
|
|
25
|
+
|
|
26
|
+
export const reOnboardingStep = atom<OnboardingStep>({
|
|
27
|
+
key: 'squad-sdk:onboarding:step',
|
|
28
|
+
default: 'welcome',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const rePostOnboardingLoadingActive = atom<boolean>({
|
|
32
|
+
key: 'squad-sdk:onboarding:postLoadingActive',
|
|
33
|
+
default: false,
|
|
34
|
+
});
|