@tastic/shell 0.1.1 → 0.2.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/README.md +71 -12
- package/dist/index.d.mts +86 -14
- package/dist/index.d.ts +86 -14
- package/dist/index.js +337 -165
- package/dist/index.mjs +327 -156
- package/package.json +9 -9
- package/src/PackContext.tsx +2 -2
- package/src/RootLayout.tsx +110 -28
- package/src/components/GuideHost.tsx +62 -23
- package/src/components/Providers.tsx +17 -7
- package/src/components/SeatTwoPanel.tsx +2 -1
- package/src/components/SettingsDialog.tsx +6 -4
- package/src/components/UpdateDialog.tsx +1 -1
- package/src/hooks/useSeatColors.ts +54 -27
- package/src/index.ts +1 -1
- package/src/redux/createPackStore.ts +16 -6
- package/src/redux/shellState.ts +4 -2
- package/src/routes.ts +10 -0
- package/src/screens/GameRoute.tsx +15 -7
- package/src/screens/HomeScreen.tsx +145 -76
- package/src/screens/LoadoutScreen.tsx +13 -7
- package/src/screens/ProfilesRoute.tsx +26 -1
- package/src/screens/StatsScreen.tsx +138 -56
- package/src/types.ts +143 -10
package/dist/index.mjs
CHANGED
|
@@ -26,12 +26,13 @@ import { useAutoPaperTheme } from "@rific/auto-paper";
|
|
|
26
26
|
import { useHapticSettings } from "@rific/feedback-press";
|
|
27
27
|
import { Toaster, ToastProvider } from "@rific/toaster";
|
|
28
28
|
import { OrientationProvider, useRotationAwareStatusBar, useThemedRootBackground } from "@tastic/core";
|
|
29
|
-
import { RotatedFrame } from "@tastic/hud";
|
|
29
|
+
import { RotatedFrame } from "@tastic/hud/chrome";
|
|
30
|
+
import { ProfileColorProvider } from "@tastic/profile";
|
|
30
31
|
import * as Haptics from "expo-haptics";
|
|
31
|
-
import { Stack } from "expo-router";
|
|
32
|
+
import { DarkTheme, Stack, ThemeProvider as NavigationThemeProvider } from "expo-router";
|
|
32
33
|
import { DeviceMotion } from "expo-sensors";
|
|
33
|
-
import { useCallback as useCallback6 } from "react";
|
|
34
|
-
import { StyleSheet } from "react-native";
|
|
34
|
+
import { useCallback as useCallback6, useMemo as useMemo3 } from "react";
|
|
35
|
+
import { Platform as Platform2, StyleSheet, View } from "react-native";
|
|
35
36
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
36
37
|
import * as RNPaper2 from "react-native-paper";
|
|
37
38
|
import { SafeAreaProvider } from "react-native-safe-area-context";
|
|
@@ -75,24 +76,35 @@ function seatTwoFor(saved, choices) {
|
|
|
75
76
|
function humanSeatsFor(mode) {
|
|
76
77
|
return mode === "twoPlayer" ? [1, 2] : [1];
|
|
77
78
|
}
|
|
79
|
+
var GUEST_PLAYER_ID = "guest";
|
|
78
80
|
|
|
79
81
|
// src/components/GuideHost.tsx
|
|
80
82
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
83
|
+
var PACK_GUIDE = "__pack__";
|
|
84
|
+
var NO_INPUT = {};
|
|
81
85
|
var GameGuideContext = createContext2(null);
|
|
82
86
|
function GuideHost({ children }) {
|
|
83
87
|
const pack = usePack();
|
|
84
|
-
const [
|
|
85
|
-
const
|
|
88
|
+
const [subject, setSubject] = useState(() => pack.guide ? PACK_GUIDE : pack.games.find((g) => g.guide)?.id ?? null);
|
|
89
|
+
const packGuide = subject === PACK_GUIDE ? pack.guide : void 0;
|
|
90
|
+
const game = subject === PACK_GUIDE ? void 0 : pack.games.find((g) => g.id === subject);
|
|
91
|
+
const gameGuide = game?.guide;
|
|
86
92
|
const rotation = useRotation();
|
|
87
93
|
const touch = useIsTouchPrimaryDevice();
|
|
88
94
|
const seatTwo = useSelector((state) => state.opponent.seatTwo);
|
|
89
|
-
const input = useSelector((state) =>
|
|
90
|
-
const seenVersion = useSelector((state) =>
|
|
95
|
+
const input = useSelector((state) => gameGuide ? gameGuide.selectInput(state) : NO_INPUT, shallowEqual);
|
|
96
|
+
const seenVersion = useSelector((state) => (packGuide ?? gameGuide)?.selectVersionSeen(state) ?? 0);
|
|
91
97
|
const dispatch = useDispatch();
|
|
92
|
-
const mode = roundModeFor(seatTwoFor(seatTwo, game
|
|
93
|
-
const steps = useMemo(() =>
|
|
94
|
-
const onSeen = useCallback(
|
|
95
|
-
|
|
98
|
+
const mode = roundModeFor(seatTwoFor(seatTwo, game?.seatTwo));
|
|
99
|
+
const steps = useMemo(() => packGuide ? packGuide.getSteps({ touch }) : gameGuide ? gameGuide.getSteps(input, { touch, mode }) : [], [packGuide, gameGuide, input, touch, mode]);
|
|
100
|
+
const onSeen = useCallback(
|
|
101
|
+
(version) => {
|
|
102
|
+
const guide = packGuide ?? gameGuide;
|
|
103
|
+
if (guide) dispatch(guide.markSeen(version));
|
|
104
|
+
},
|
|
105
|
+
[dispatch, packGuide, gameGuide]
|
|
106
|
+
);
|
|
107
|
+
return /* @__PURE__ */ jsx3(GuideProvider, { steps, currentVersion: (packGuide ?? gameGuide)?.version ?? 0, seenVersion, onSeen, rotation, children: /* @__PURE__ */ jsx3(GameGuideBridge, { onPick: setSubject, children }) });
|
|
96
108
|
}
|
|
97
109
|
function GameGuideBridge({ onPick, children }) {
|
|
98
110
|
const pack = usePack();
|
|
@@ -100,6 +112,7 @@ function GameGuideBridge({ onPick, children }) {
|
|
|
100
112
|
const { isOpen, open } = useGuide();
|
|
101
113
|
const pendingNext = useRef(null);
|
|
102
114
|
const wasOpen = useRef(false);
|
|
115
|
+
const packAutoShown = useRef(false);
|
|
103
116
|
useEffect(() => {
|
|
104
117
|
if (wasOpen.current && !isOpen && pendingNext.current) {
|
|
105
118
|
const next = pendingNext.current;
|
|
@@ -110,15 +123,16 @@ function GameGuideBridge({ onPick, children }) {
|
|
|
110
123
|
}, [isOpen]);
|
|
111
124
|
const openFor = useCallback(
|
|
112
125
|
(gameId) => {
|
|
126
|
+
if (!pack.games.find((g) => g.id === gameId)?.guide) return;
|
|
113
127
|
onPick(gameId);
|
|
114
128
|
open();
|
|
115
129
|
},
|
|
116
|
-
[onPick, open]
|
|
130
|
+
[pack, onPick, open]
|
|
117
131
|
);
|
|
118
132
|
const openFirstTime = useCallback(
|
|
119
133
|
(gameId, next) => {
|
|
120
|
-
const
|
|
121
|
-
if (!
|
|
134
|
+
const guide = pack.games.find((g) => g.id === gameId)?.guide;
|
|
135
|
+
if (!guide || guide.selectVersionSeen(store.getState()) >= guide.version) {
|
|
122
136
|
next();
|
|
123
137
|
return;
|
|
124
138
|
}
|
|
@@ -127,7 +141,18 @@ function GameGuideBridge({ onPick, children }) {
|
|
|
127
141
|
},
|
|
128
142
|
[pack, store, openFor]
|
|
129
143
|
);
|
|
130
|
-
const
|
|
144
|
+
const openPack = useCallback(() => {
|
|
145
|
+
if (!pack.guide) return;
|
|
146
|
+
onPick(PACK_GUIDE);
|
|
147
|
+
open();
|
|
148
|
+
}, [pack, onPick, open]);
|
|
149
|
+
const autoShowPack = useCallback(() => {
|
|
150
|
+
const guide = pack.guide;
|
|
151
|
+
if (!guide || packAutoShown.current || guide.selectVersionSeen(store.getState()) >= guide.version) return;
|
|
152
|
+
packAutoShown.current = true;
|
|
153
|
+
openPack();
|
|
154
|
+
}, [pack, store, openPack]);
|
|
155
|
+
const value = useMemo(() => ({ openFirstTime, openFor, openPack, autoShowPack }), [openFirstTime, openFor, openPack, autoShowPack]);
|
|
131
156
|
return /* @__PURE__ */ jsx3(GameGuideContext.Provider, { value, children });
|
|
132
157
|
}
|
|
133
158
|
function useGameGuide() {
|
|
@@ -147,15 +172,20 @@ import * as RNPaper from "react-native-paper";
|
|
|
147
172
|
import { Provider as ReduxProvider, useDispatch as useDispatch2, useSelector as useSelector2 } from "react-redux";
|
|
148
173
|
import { PersistGate } from "redux-persist/integration/react";
|
|
149
174
|
import { Fragment as Fragment2, jsx as jsx4 } from "react/jsx-runtime";
|
|
175
|
+
function useShellFeedbackSounds() {
|
|
176
|
+
const selection = useAudioPool(__require("./select-MLBNNA4N.wav"));
|
|
177
|
+
const notification = useAudioPool(__require("./notification-CGGFPYK7.wav"));
|
|
178
|
+
return { selection, notification };
|
|
179
|
+
}
|
|
150
180
|
var FeedbackBridge = ({ children }) => {
|
|
151
181
|
const haptic = useSelector2((state) => state.haptic);
|
|
152
182
|
const sound = useSelector2((state) => state.sound);
|
|
153
183
|
const dispatch = useDispatch2();
|
|
154
184
|
const onChange = useCallback2((next) => dispatch(hapticActions.initialize(next)), [dispatch]);
|
|
155
185
|
const onSoundChange = useCallback2((next) => dispatch(soundActions.initialize(next)), [dispatch]);
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
const bridgeProps = useFeedbackBridgeProps({ initialValue: haptic, onChange, soundInitialValue: sound, onSoundChange, sound:
|
|
186
|
+
const { useFeedbackSounds = useShellFeedbackSounds } = usePack();
|
|
187
|
+
const sounds = useFeedbackSounds();
|
|
188
|
+
const bridgeProps = useFeedbackBridgeProps({ initialValue: haptic, onChange, soundInitialValue: sound, onSoundChange, sound: sounds });
|
|
159
189
|
return /* @__PURE__ */ jsx4(FeedbackPressProvider, { ...bridgeProps, paper: RNPaper, children });
|
|
160
190
|
};
|
|
161
191
|
var EdgeGuardBridge = ({ children }) => {
|
|
@@ -198,7 +228,7 @@ function Theme({ children }) {
|
|
|
198
228
|
// src/components/UpdateDialog.tsx
|
|
199
229
|
import { useUpdateErrorToast } from "@rific/toaster";
|
|
200
230
|
import { useRotation as useRotation2 } from "@tastic/core";
|
|
201
|
-
import { UpdateDialog as SharedUpdateDialog } from "@tastic/hud";
|
|
231
|
+
import { UpdateDialog as SharedUpdateDialog } from "@tastic/hud/chrome";
|
|
202
232
|
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
203
233
|
function UpdateDialog() {
|
|
204
234
|
const onError = useUpdateErrorToast();
|
|
@@ -371,20 +401,41 @@ function useProfiles() {
|
|
|
371
401
|
}
|
|
372
402
|
|
|
373
403
|
// src/RootLayout.tsx
|
|
374
|
-
import { jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
404
|
+
import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
375
405
|
var PLAY_OPTIONS = { statusBarHidden: true };
|
|
406
|
+
var KEEP_STATUS_BAR_OPTIONS = {};
|
|
407
|
+
var TRANSPARENT_CONTENT = { backgroundColor: "transparent" };
|
|
408
|
+
var noScreenOptions = () => ({});
|
|
376
409
|
function AppStack() {
|
|
410
|
+
const pack = usePack();
|
|
411
|
+
const { Backdrop } = pack;
|
|
377
412
|
const { dark } = useAutoPaperTheme();
|
|
378
|
-
const rootBackground = useThemedRootBackground(dark, false);
|
|
413
|
+
const rootBackground = useThemedRootBackground(Backdrop ? true : dark, false);
|
|
379
414
|
const statusBar = useRotationAwareStatusBar();
|
|
380
|
-
|
|
415
|
+
const packOptions = (pack.useScreenOptions ?? noScreenOptions)();
|
|
416
|
+
const backdropOptions = Backdrop ? { contentStyle: TRANSPARENT_CONTENT, animation: Platform2.select({ android: "fade" }) } : void 0;
|
|
417
|
+
const playOptions = useCallback6(
|
|
418
|
+
({ route }) => {
|
|
419
|
+
const id = route.params?.game;
|
|
420
|
+
return pack.games.some((g) => g.id === id && g.keepStatusBar) ? KEEP_STATUS_BAR_OPTIONS : PLAY_OPTIONS;
|
|
421
|
+
},
|
|
422
|
+
[pack.games]
|
|
423
|
+
);
|
|
424
|
+
const navigationTheme = useMemo3(() => Backdrop ? { ...DarkTheme, colors: { ...DarkTheme.colors, background: "transparent" } } : null, [Backdrop]);
|
|
425
|
+
const stack = /* @__PURE__ */ jsxs2(Stack, { screenOptions: { ...rootBackground, ...statusBar, ...backdropOptions, ...packOptions }, children: [
|
|
381
426
|
/* @__PURE__ */ jsx9(Stack.Screen, { name: "index" }),
|
|
382
|
-
/* @__PURE__ */ jsx9(Stack.Screen, { name: "play/[game]", options:
|
|
427
|
+
/* @__PURE__ */ jsx9(Stack.Screen, { name: "play/[game]", options: playOptions })
|
|
428
|
+
] });
|
|
429
|
+
if (!Backdrop || !navigationTheme) return stack;
|
|
430
|
+
return /* @__PURE__ */ jsxs2(View, { style: styles.root, children: [
|
|
431
|
+
/* @__PURE__ */ jsx9(Backdrop, {}),
|
|
432
|
+
/* @__PURE__ */ jsx9(NavigationThemeProvider, { value: navigationTheme, children: stack })
|
|
383
433
|
] });
|
|
384
434
|
}
|
|
385
435
|
function OrientationBridge({ children }) {
|
|
386
436
|
const { settings, setSettings } = useAppSettings();
|
|
387
|
-
|
|
437
|
+
const { portraitWhileKeyboard } = usePack();
|
|
438
|
+
return /* @__PURE__ */ jsx9(OrientationProvider, { deviceMotion: DeviceMotion, freezeWhileLocked: true, lockInitialValue: { locked: settings.lockOrientation, rotation: settings.lockedRotation }, onLockChange: (next) => setSettings({ lockOrientation: next.locked, lockedRotation: next.rotation }), portraitWhileKeyboard, children });
|
|
388
439
|
}
|
|
389
440
|
function GatedApp({ children }) {
|
|
390
441
|
const { loaded } = useProfiles();
|
|
@@ -392,7 +443,8 @@ function GatedApp({ children }) {
|
|
|
392
443
|
}
|
|
393
444
|
function HapticGatedToastProvider({ children }) {
|
|
394
445
|
const { settings } = useHapticSettings();
|
|
395
|
-
|
|
446
|
+
const { Toaster: PackToasterComponent } = usePack();
|
|
447
|
+
return /* @__PURE__ */ jsx9(ToastProvider, { haptics: settings.vibrate ? Haptics : void 0, paper: PackToasterComponent ? void 0 : RNPaper2, children });
|
|
396
448
|
}
|
|
397
449
|
function RotatingToaster() {
|
|
398
450
|
const renderFrame = useCallback6(
|
|
@@ -401,15 +453,28 @@ function RotatingToaster() {
|
|
|
401
453
|
);
|
|
402
454
|
return /* @__PURE__ */ jsx9(Toaster, { historyButton: null, clearButton: null, limit: 3, renderFrame });
|
|
403
455
|
}
|
|
456
|
+
function PackToaster({ Toaster: PackToasterComponent }) {
|
|
457
|
+
return /* @__PURE__ */ jsx9(View, { style: StyleSheet.absoluteFill, pointerEvents: "box-none", children: /* @__PURE__ */ jsx9(RotatedFrame, { rotatedSafeArea: true, passThrough: true, children: /* @__PURE__ */ jsx9(PackToasterComponent, {}) }) });
|
|
458
|
+
}
|
|
404
459
|
function GameProviders({ games, children }) {
|
|
405
|
-
return games.reduceRight((inner, game) => /* @__PURE__ */ jsx9(game.Provider, { children: inner }, game.id), children);
|
|
460
|
+
return games.reduceRight((inner, game) => game.Provider ? /* @__PURE__ */ jsx9(game.Provider, { children: inner }, game.id) : inner, children);
|
|
461
|
+
}
|
|
462
|
+
function PreviewColors({ previewColor, children }) {
|
|
463
|
+
const { dark } = useAutoPaperTheme();
|
|
464
|
+
const preview = useCallback6((hex) => previewColor(hex, dark), [previewColor, dark]);
|
|
465
|
+
return /* @__PURE__ */ jsx9(ProfileColorProvider, { preview, children });
|
|
466
|
+
}
|
|
467
|
+
function PackRoot({ pack, children }) {
|
|
468
|
+
const { Provider, previewColor } = pack;
|
|
469
|
+
const inner = Provider ? /* @__PURE__ */ jsx9(Provider, { children }) : children;
|
|
470
|
+
return previewColor ? /* @__PURE__ */ jsx9(PreviewColors, { previewColor, children: inner }) : /* @__PURE__ */ jsx9(Fragment3, { children: inner });
|
|
406
471
|
}
|
|
407
472
|
function RootLayout({ pack, store, persistor }) {
|
|
408
|
-
return /* @__PURE__ */ jsx9(GestureHandlerRootView, { style: styles.root, children: /* @__PURE__ */ jsx9(SafeAreaProvider, { children: /* @__PURE__ */ jsx9(PackProvider, { pack, children: /* @__PURE__ */ jsx9(Fonts, { children: /* @__PURE__ */ jsx9(Providers, { store, persistor, children: /* @__PURE__ */ jsx9(Theme, { children: /* @__PURE__ */ jsx9(ProfilesProvider, { children: /* @__PURE__ */ jsx9(LastActiveGameProvider, { children: /* @__PURE__ */ jsx9(GameProviders, { games: pack.games, children: /* @__PURE__ */ jsx9(GatedApp, { children: /* @__PURE__ */ jsx9(OrientationBridge, { children: /* @__PURE__ */ jsxs2(HapticGatedToastProvider, { children: [
|
|
473
|
+
return /* @__PURE__ */ jsx9(GestureHandlerRootView, { style: styles.root, children: /* @__PURE__ */ jsx9(SafeAreaProvider, { children: /* @__PURE__ */ jsx9(PackProvider, { pack, children: /* @__PURE__ */ jsx9(Fonts, { children: /* @__PURE__ */ jsx9(Providers, { store, persistor, children: /* @__PURE__ */ jsx9(Theme, { children: /* @__PURE__ */ jsx9(ProfilesProvider, { children: /* @__PURE__ */ jsx9(LastActiveGameProvider, { children: /* @__PURE__ */ jsx9(GameProviders, { games: pack.games, children: /* @__PURE__ */ jsx9(PackRoot, { pack, children: /* @__PURE__ */ jsx9(GatedApp, { children: /* @__PURE__ */ jsx9(OrientationBridge, { children: /* @__PURE__ */ jsxs2(HapticGatedToastProvider, { children: [
|
|
409
474
|
/* @__PURE__ */ jsx9(GuideHost, { children: /* @__PURE__ */ jsx9(AppStack, {}) }),
|
|
410
|
-
/* @__PURE__ */ jsx9(RotatingToaster, {}),
|
|
475
|
+
pack.Toaster ? /* @__PURE__ */ jsx9(PackToaster, { Toaster: pack.Toaster }) : /* @__PURE__ */ jsx9(RotatingToaster, {}),
|
|
411
476
|
/* @__PURE__ */ jsx9(UpdateDialog, {})
|
|
412
|
-
] }) }) }) }) }) }) }) }) }) }) }) });
|
|
477
|
+
] }) }) }) }) }) }) }) }) }) }) }) }) });
|
|
413
478
|
}
|
|
414
479
|
var styles = StyleSheet.create({
|
|
415
480
|
root: { flex: 1 }
|
|
@@ -418,24 +483,32 @@ var styles = StyleSheet.create({
|
|
|
418
483
|
// src/screens/GameRoute.tsx
|
|
419
484
|
import { Redirect, useLocalSearchParams } from "expo-router";
|
|
420
485
|
import { useEffect as useEffect5 } from "react";
|
|
486
|
+
import { AppState } from "react-native";
|
|
487
|
+
|
|
488
|
+
// src/routes.ts
|
|
489
|
+
var playHref = (game) => ({ pathname: "/play/[game]", params: { game } });
|
|
490
|
+
var loadoutHref = (game) => ({ pathname: "/loadout/[game]", params: { game } });
|
|
491
|
+
var STATS_HREF = "/stats";
|
|
492
|
+
var PROFILES_HREF = "/profiles";
|
|
421
493
|
|
|
422
494
|
// src/screens/LoadoutScreen.tsx
|
|
423
495
|
import { defaultColors, useAutoPaperTheme as useAutoPaperTheme3 } from "@rific/auto-paper";
|
|
424
496
|
import { FakeLandscapeView, getViewRotation, rotateInsets, useOrientationState } from "@tastic/core";
|
|
425
|
-
import { CornerActionButtons,
|
|
497
|
+
import { CornerActionButtons, PressAwayOverlay, usePopoverHost } from "@tastic/hud/chrome";
|
|
498
|
+
import { PlayerSetupPanel, ReadyButton as ReadyButton2, SharedActionBand } from "@tastic/hud/lobby";
|
|
426
499
|
import { DualZoneLayout, needsSharedNeutralZone, useDualZoneLayout } from "@tastic/split-screen";
|
|
427
500
|
import { router as router2, useFocusEffect as useFocusEffect2 } from "expo-router";
|
|
428
|
-
import { useCallback as useCallback9, useEffect as useEffect4, useMemo as
|
|
429
|
-
import { Platform as
|
|
501
|
+
import { useCallback as useCallback9, useEffect as useEffect4, useMemo as useMemo5, useRef as useRef4, useState as useState4 } from "react";
|
|
502
|
+
import { Platform as Platform3, StyleSheet as StyleSheet3, View as View3 } from "react-native";
|
|
430
503
|
import Animated from "react-native-reanimated";
|
|
431
504
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
432
505
|
import { useDispatch as useDispatch9 } from "react-redux";
|
|
433
506
|
|
|
434
507
|
// src/components/SeatTwoPanel.tsx
|
|
435
|
-
import { ReadyButton, useZoneClampedAlign } from "@tastic/hud";
|
|
508
|
+
import { ReadyButton, useZoneClampedAlign } from "@tastic/hud/lobby";
|
|
436
509
|
import { getOptionPickerContentSize, OptionPicker } from "@tastic/profile";
|
|
437
|
-
import { useMemo as
|
|
438
|
-
import { StyleSheet as StyleSheet2, View } from "react-native";
|
|
510
|
+
import { useMemo as useMemo4 } from "react";
|
|
511
|
+
import { StyleSheet as StyleSheet2, View as View2 } from "react-native";
|
|
439
512
|
import { jsx as jsx10, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
440
513
|
var CPU_PREFIX = "cpu:";
|
|
441
514
|
var PROFILE_PREFIX = "profile:";
|
|
@@ -451,7 +524,7 @@ function SeatTwoPanel({ idPrefix, host, choices, seatTwo, onSeatTwoChange, cpuDi
|
|
|
451
524
|
const isPerson = seatTwo === "person";
|
|
452
525
|
const selectedProfile = isPerson ? profiles.find((p) => p.id === selectedProfileId) ?? null : null;
|
|
453
526
|
const cpuOptions = cpuDifficulty?.options;
|
|
454
|
-
const options =
|
|
527
|
+
const options = useMemo4(() => {
|
|
455
528
|
const list = [];
|
|
456
529
|
if (choices.includes("cpu")) list.push(...cpuOptions ? cpuOptions.map((o) => ({ value: `${CPU_PREFIX}${o.value}`, label: o.label, icon: o.icon })) : [{ value: CPU_PREFIX, label: "CPU", icon: CPU_ICON }]);
|
|
457
530
|
if (choices.includes("nobody")) list.push({ value: NOBODY, label: "Nobody", icon: NOBODY_ICON });
|
|
@@ -480,9 +553,9 @@ function SeatTwoPanel({ idPrefix, host, choices, seatTwo, onSeatTwoChange, cpuDi
|
|
|
480
553
|
const align = useZoneClampedAlign(host.openId === pickerId, size.width, size.height);
|
|
481
554
|
const ownPopoverOpen = host.openId?.startsWith(`${idPrefix}-`) ?? false;
|
|
482
555
|
const pickerRowOpen = secondPickerId !== void 0 && host.openId === secondPickerId;
|
|
483
|
-
return /* @__PURE__ */ jsxs3(
|
|
556
|
+
return /* @__PURE__ */ jsxs3(View2, { style: [styles2.panel, ownPopoverOpen && styles2.panelOpen], children: [
|
|
484
557
|
/* @__PURE__ */ jsx10(OptionPicker, { id: pickerId, host, options, value, onChange: handleChange, color, dark, icon, colorSection, alignOverride: align }),
|
|
485
|
-
isPerson && secondPicker && /* @__PURE__ */ jsx10(
|
|
558
|
+
isPerson && secondPicker && /* @__PURE__ */ jsx10(View2, { style: [styles2.pickerRow, pickerRowOpen && styles2.pickerRowOpen], children: secondPicker }),
|
|
486
559
|
isPerson && showReadyButton && onToggleReady && /* @__PURE__ */ jsx10(ReadyButton, { color, ready: ready ?? false, onToggleReady })
|
|
487
560
|
] });
|
|
488
561
|
}
|
|
@@ -509,13 +582,14 @@ var styles2 = StyleSheet2.create({
|
|
|
509
582
|
|
|
510
583
|
// src/components/SettingsDialog.tsx
|
|
511
584
|
import { useUpdateErrorToast as useUpdateErrorToast2 } from "@rific/toaster";
|
|
512
|
-
import { BaseSettingsDialog } from "@tastic/hud";
|
|
585
|
+
import { BaseSettingsDialog } from "@tastic/hud/chrome";
|
|
513
586
|
import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
514
587
|
function SettingsDialog({ visible, onDismiss, rotation = 0, onShowHowToPlay, children }) {
|
|
515
588
|
const onUpdateError = useUpdateErrorToast2();
|
|
516
589
|
const { settings, setSettings } = useAppSettings();
|
|
517
|
-
const { otaVersion, SettingsSections } = usePack();
|
|
518
|
-
|
|
590
|
+
const { otaVersion, SettingsSections, edgeGuard = true } = usePack();
|
|
591
|
+
const edgeGuardProps = edgeGuard ? { deferBottomEdgeGestures: settings.deferBottomEdgeGestures, onDeferBottomEdgeGestures: (value) => setSettings({ deferBottomEdgeGestures: value }) } : {};
|
|
592
|
+
return /* @__PURE__ */ jsxs4(BaseSettingsDialog, { visible, onDismiss, rotation, version: otaVersion, lockOrientation: settings.lockOrientation, onLockOrientationChange: (value) => setSettings({ lockOrientation: value }), ...edgeGuardProps, onUpdateError, onShowHowToPlay, children: [
|
|
519
593
|
children,
|
|
520
594
|
SettingsSections && /* @__PURE__ */ jsx11(SettingsSections, {})
|
|
521
595
|
] });
|
|
@@ -560,13 +634,17 @@ function createSeatColorsReducer(seatColors) {
|
|
|
560
634
|
var seatColorsActions = createSeatColorsSlice("pack", defaultSeatColorsState({ 1: "", 2: "" })).actions;
|
|
561
635
|
|
|
562
636
|
// src/hooks/useSeatColors.ts
|
|
637
|
+
function sameTheme(current, next) {
|
|
638
|
+
const a = current.color;
|
|
639
|
+
const b = next.color;
|
|
640
|
+
const sameColor = typeof a === "string" || typeof b === "string" ? typeof a === typeof b && String(a).toLowerCase() === String(b).toLowerCase() : !!a && !!b && a.primary.toLowerCase() === b.primary.toLowerCase() && a.secondary.toLowerCase() === b.secondary.toLowerCase();
|
|
641
|
+
return sameColor && (next.harmony === void 0 || next.harmony === current.harmony);
|
|
642
|
+
}
|
|
563
643
|
function useSeatColors(p2IsHuman) {
|
|
564
|
-
const { colors: themeColors } = useAutoPaperTheme2();
|
|
565
|
-
const { set: setThemeColor } = useThemeSettings();
|
|
566
|
-
const p1Color = themeColors.primary;
|
|
567
|
-
const p2Color = themeColors.secondary;
|
|
644
|
+
const { dark, colors: themeColors } = useAutoPaperTheme2();
|
|
645
|
+
const { settings: themeSettings, set: setThemeColor } = useThemeSettings();
|
|
568
646
|
const { profiles, lastSelected } = useProfiles();
|
|
569
|
-
const { seatColors } = usePack();
|
|
647
|
+
const { seatColors, seatTheme } = usePack();
|
|
570
648
|
const seatDefaultColors = [seatColors[1], seatColors[2]];
|
|
571
649
|
const dispatch = useDispatch8();
|
|
572
650
|
const guestColors = useSelector7((state) => state.seatColors.lastGuestColor);
|
|
@@ -574,11 +652,21 @@ function useSeatColors(p2IsHuman) {
|
|
|
574
652
|
const profileColors = useSelector7((state) => state.seatColors.profileColors);
|
|
575
653
|
const p1Profile = profiles.find((p) => p.id === lastSelected[1]) ?? null;
|
|
576
654
|
const p2Profile = p2IsHuman ? profiles.find((p) => p.id === lastSelected[2]) ?? null : null;
|
|
655
|
+
const rememberedColors = (p1Prof, p2Prof, picks, guest, cpu) => {
|
|
656
|
+
const p1 = p1Prof ? profileSeatColor(p1Prof, picks) : guest[1];
|
|
657
|
+
const p2 = avoidSeatColorClash(p1, p2Prof ? profileSeatColor(p2Prof, picks) : p2IsHuman ? guest[2] : cpu, seatDefaultColors);
|
|
658
|
+
return { p1, p2 };
|
|
659
|
+
};
|
|
660
|
+
const remembered = rememberedColors(p1Profile, p2Profile, profileColors, guestColors, cpuColor);
|
|
661
|
+
const p1Color = seatTheme ? remembered.p1 : themeColors.primary;
|
|
662
|
+
const p2Color = seatTheme ? remembered.p2 : themeColors.secondary;
|
|
663
|
+
const themeFor = useCallback8((p1, p2) => seatTheme ? seatTheme({ 1: p1, 2: p2 }, dark) : { color: { primary: p1, secondary: p2, tertiary: getThirdColor(p1, p2) } }, [seatTheme, dark]);
|
|
577
664
|
const p1ProfileRef = useRef3(p1Profile);
|
|
578
665
|
const p2ProfileRef = useRef3(p2Profile);
|
|
579
666
|
const guestColorsRef = useRef3(guestColors);
|
|
580
667
|
const cpuColorRef = useRef3(cpuColor);
|
|
581
668
|
const profileColorsRef = useRef3(profileColors);
|
|
669
|
+
const themeForRef = useRef3(themeFor);
|
|
582
670
|
useEffect3(() => {
|
|
583
671
|
p1ProfileRef.current = p1Profile;
|
|
584
672
|
}, [p1Profile]);
|
|
@@ -595,21 +683,17 @@ function useSeatColors(p2IsHuman) {
|
|
|
595
683
|
profileColorsRef.current = profileColors;
|
|
596
684
|
}, [profileColors]);
|
|
597
685
|
useEffect3(() => {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
686
|
+
themeForRef.current = themeFor;
|
|
687
|
+
}, [themeFor]);
|
|
688
|
+
useEffect3(() => {
|
|
689
|
+
const next = themeFor(remembered.p1, remembered.p2);
|
|
690
|
+
if (sameTheme(themeSettings, next)) return;
|
|
691
|
+
setThemeColor(next);
|
|
692
|
+
}, [lastSelected[1], lastSelected[2], p2IsHuman, guestColors, cpuColor, profileColors, profiles.length, dark]);
|
|
603
693
|
useFocusEffect(
|
|
604
694
|
useCallback8(() => {
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
const p1Prof = p1ProfileRef.current;
|
|
608
|
-
const p2Prof = p2ProfileRef.current;
|
|
609
|
-
const picks = profileColorsRef.current;
|
|
610
|
-
const nextP1 = p1Prof ? profileSeatColor(p1Prof, picks) : guest[1];
|
|
611
|
-
const nextP2 = avoidSeatColorClash(nextP1, p2Prof ? profileSeatColor(p2Prof, picks) : p2IsHuman ? guest[2] : cpu, seatDefaultColors);
|
|
612
|
-
setThemeColor({ color: { primary: nextP1, secondary: nextP2, tertiary: getThirdColor(nextP1, nextP2) } });
|
|
695
|
+
const next = rememberedColors(p1ProfileRef.current, p2ProfileRef.current, profileColorsRef.current, guestColorsRef.current, cpuColorRef.current);
|
|
696
|
+
setThemeColor(themeForRef.current(next.p1, next.p2));
|
|
613
697
|
}, [lastSelected[1], lastSelected[2], p2IsHuman, setThemeColor])
|
|
614
698
|
);
|
|
615
699
|
const persistSeatColor = useCallback8(
|
|
@@ -631,21 +715,21 @@ function useSeatColors(p2IsHuman) {
|
|
|
631
715
|
(hex) => {
|
|
632
716
|
const swapped = hex.toLowerCase() === p2Color.toLowerCase();
|
|
633
717
|
const nextP2 = swapped ? p1Color : p2Color;
|
|
634
|
-
setThemeColor(
|
|
718
|
+
setThemeColor(themeFor(hex, nextP2));
|
|
635
719
|
persistSeatColor(1, hex);
|
|
636
720
|
if (swapped) persistSeatColor(2, nextP2);
|
|
637
721
|
},
|
|
638
|
-
[setThemeColor, p1Color, p2Color, persistSeatColor]
|
|
722
|
+
[setThemeColor, themeFor, p1Color, p2Color, persistSeatColor]
|
|
639
723
|
);
|
|
640
724
|
const handleP2ColorChange = useCallback8(
|
|
641
725
|
(hex) => {
|
|
642
726
|
const swapped = hex.toLowerCase() === p1Color.toLowerCase();
|
|
643
727
|
const nextP1 = swapped ? p2Color : p1Color;
|
|
644
|
-
setThemeColor(
|
|
728
|
+
setThemeColor(themeFor(nextP1, hex));
|
|
645
729
|
persistSeatColor(2, hex);
|
|
646
730
|
if (swapped) persistSeatColor(1, nextP1);
|
|
647
731
|
},
|
|
648
|
-
[setThemeColor, p1Color, p2Color, persistSeatColor]
|
|
732
|
+
[setThemeColor, themeFor, p1Color, p2Color, persistSeatColor]
|
|
649
733
|
);
|
|
650
734
|
return { p1Color, p2Color, handleP1ColorChange, handleP2ColorChange };
|
|
651
735
|
}
|
|
@@ -671,16 +755,17 @@ function LoadoutScreen({ game }) {
|
|
|
671
755
|
const { seatTwo: savedSeatTwo, setSeatTwo } = useOpponent();
|
|
672
756
|
const seatTwo = seatTwoFor(savedSeatTwo, game.seatTwo);
|
|
673
757
|
const mode = roundModeFor(seatTwo);
|
|
674
|
-
const humanSeats =
|
|
758
|
+
const humanSeats = useMemo5(() => humanSeatsFor(mode), [mode]);
|
|
675
759
|
const p2IsHuman = mode === "twoPlayer";
|
|
676
760
|
const p2Exists = mode !== "solo";
|
|
677
761
|
const loadout = game.useLoadout(mode);
|
|
678
762
|
const { openFor } = useGameGuide();
|
|
679
763
|
const { profiles, lastSelected, selectProfile } = useProfiles();
|
|
680
764
|
const { orientationMode, p1OnRight, upsideDown, resolved: p1OnRightResolved } = useOrientationState();
|
|
681
|
-
const fixedSeats = p2IsHuman && (!!game.twoPlayerSeats?.faceToFaceOnWeb ||
|
|
765
|
+
const fixedSeats = p2IsHuman && (!!game.twoPlayerSeats?.faceToFaceOnWeb || Platform3.OS !== "web");
|
|
682
766
|
const fixedUpsideDown = !!game.twoPlayerSeats?.followUpsideDown && orientationMode === "faceToFace" && upsideDown;
|
|
683
767
|
const { panelLayout, panelFadeStyle } = useDualZoneLayout(fixedSeats ? "faceToFace" : orientationMode, fixedSeats ? true : p1OnRight, fixedSeats || p1OnRightResolved, fixedSeats ? fixedUpsideDown : upsideDown, PANEL_SWAP_FADE_MS);
|
|
768
|
+
const playersRowFade = panelFadeStyle;
|
|
684
769
|
const [settingsOpen, setSettingsOpen] = useState4(false);
|
|
685
770
|
const rotation = getViewRotation(panelLayout.orientationMode, panelLayout.p1OnRight, panelLayout.upsideDown);
|
|
686
771
|
const insets = rotateInsets(useSafeAreaInsets(), rotation);
|
|
@@ -709,7 +794,7 @@ function LoadoutScreen({ game }) {
|
|
|
709
794
|
useEffect4(() => {
|
|
710
795
|
if (!humanSeats.every((s) => ready[s])) return;
|
|
711
796
|
dispatch(liveplayActions.setActiveRound({ game: game.id, mode, settings: roundSettingsRef.current }));
|
|
712
|
-
router2.push(
|
|
797
|
+
router2.push(playHref(game.id));
|
|
713
798
|
}, [ready, humanSeats, game.id, mode, dispatch]);
|
|
714
799
|
const bg = dark ? "#000000" : "#FFFFFF";
|
|
715
800
|
const fg = dark ? "#FFFFFF" : "#000000";
|
|
@@ -737,10 +822,10 @@ function LoadoutScreen({ game }) {
|
|
|
737
822
|
const sharedControls = showMetaInSharedBand ? /* @__PURE__ */ jsx12(SharedActionBand, { onBack: safeBack, onSettings: () => setSettingsOpen(true), onRandomize: loadout.randomize, onReset: loadout.reset, fg, popoverOpen: optionsPopoverOpen, children: optionsRow }) : optionsRow;
|
|
738
823
|
const controlLook = { accentColor: themeColors.tertiary, mutedColor: fgMuted, dark, rotation };
|
|
739
824
|
const p1Control = loadout.seatControl(1, controlsHost, { ...controlLook, seatColor: p1Color });
|
|
740
|
-
const p1Panel = /* @__PURE__ */ jsx12(PlayerSetupPanel, { idPrefix: "p1", host: controlsHost, color: p1Color, onColorChange: handleP1ColorChange, swatches: defaultColors, takenColor: p2Exists ? p2Color : void 0, allowSwapTaken: mode === "vsCpu", colorCaption: pack.colorCaption, isHuman: true, ready: ready[1], onToggleReady: () => setReady((r) => ({ ...r, 1: !r[1] })), dark, showReadyButton, secondPicker: p1Control, secondPickerId: p1Control ? "p1-control" : void 0, profiles, selectedProfileId: lastSelected[1], takenProfileId: p2IsHuman ? lastSelected[2] : null, guestLabel: "P1", onProfileSelect: handleP1ProfileSelect, onManageProfiles: () => router2.push(
|
|
825
|
+
const p1Panel = /* @__PURE__ */ jsx12(PlayerSetupPanel, { idPrefix: "p1", host: controlsHost, color: p1Color, onColorChange: handleP1ColorChange, swatches: defaultColors, takenColor: p2Exists ? p2Color : void 0, allowSwapTaken: mode === "vsCpu", colorCaption: pack.colorCaption, isHuman: true, ready: ready[1], onToggleReady: () => setReady((r) => ({ ...r, 1: !r[1] })), dark, showReadyButton, secondPicker: p1Control, secondPickerId: p1Control ? "p1-control" : void 0, profiles, selectedProfileId: lastSelected[1], takenProfileId: p2IsHuman ? lastSelected[2] : null, guestLabel: "P1", onProfileSelect: handleP1ProfileSelect, onManageProfiles: () => router2.push(PROFILES_HREF) });
|
|
741
826
|
const p2Control = p2IsHuman ? loadout.seatControl(2, p2Host, { ...controlLook, seatColor: p2Color }) : void 0;
|
|
742
827
|
const p2Panel = game.seatTwo && /* @__PURE__ */ jsx12(SeatTwoPanel, { idPrefix: "p2", host: p2Host, choices: game.seatTwo, seatTwo, onSeatTwoChange: changeSeatTwo, cpuDifficulty: loadout.cpuDifficulty, profiles, selectedProfileId: lastSelected[2], onProfileSelect: handleP2ProfileSelect, color: p2Color, onColorChange: handleP2ColorChange, swatches: defaultColors, takenColor: p1Color, allowSwapTaken: mode === "vsCpu", colorCaption: pack.colorCaption, dark, secondPicker: p2Control, secondPickerId: p2Control ? "p2-control" : void 0, ready: ready[2], onToggleReady: () => setReady((r) => ({ ...r, 2: !r[2] })), showReadyButton });
|
|
743
|
-
return /* @__PURE__ */ jsxs5(
|
|
828
|
+
return /* @__PURE__ */ jsxs5(View3, { style: [styles3.container, { backgroundColor: bg }], children: [
|
|
744
829
|
/* @__PURE__ */ jsxs5(FakeLandscapeView, { orientationMode: panelLayout.orientationMode, p1OnRight: panelLayout.p1OnRight, upsideDown: panelLayout.upsideDown, style: styles3.rotatable, children: [
|
|
745
830
|
/* @__PURE__ */ jsx12(PressAwayOverlay, { active: controlsHost.openId !== null, onPress: controlsHost.close }),
|
|
746
831
|
p2ZoneStyle && /* @__PURE__ */ jsx12(PressAwayOverlay, { active: controlsHost.openId !== null || p2Host.openId !== null, onPress: p2Host.close, style: p2ZoneStyle }),
|
|
@@ -757,9 +842,9 @@ function LoadoutScreen({ game }) {
|
|
|
757
842
|
p2Elevated: p2Host.openId !== null,
|
|
758
843
|
sharedElevated: optionsPopoverOpen
|
|
759
844
|
}
|
|
760
|
-
) : /* @__PURE__ */ jsxs5(
|
|
845
|
+
) : /* @__PURE__ */ jsxs5(View3, { style: styles3.stackedZone, children: [
|
|
761
846
|
sharedControls,
|
|
762
|
-
/* @__PURE__ */ jsxs5(Animated.View, { style: [styles3.playersRow, anyPlayerPickerOpen && styles3.playersRowOpen,
|
|
847
|
+
/* @__PURE__ */ jsxs5(Animated.View, { style: [styles3.playersRow, anyPlayerPickerOpen && styles3.playersRowOpen, playersRowFade], children: [
|
|
763
848
|
!isPortrait && panelLayout.p1OnRight ? p2Panel : p1Panel,
|
|
764
849
|
!isPortrait && panelLayout.p1OnRight ? p1Panel : p2Panel
|
|
765
850
|
] }),
|
|
@@ -829,7 +914,7 @@ function LoadoutRoute() {
|
|
|
829
914
|
const { game: id } = useLocalSearchParams();
|
|
830
915
|
const game = usePackGame(id);
|
|
831
916
|
if (!game) return /* @__PURE__ */ jsx13(Redirect, { href: "/" });
|
|
832
|
-
if (!hasLoadout(game)) return /* @__PURE__ */ jsx13(Redirect, { href:
|
|
917
|
+
if (!hasLoadout(game)) return /* @__PURE__ */ jsx13(Redirect, { href: playHref(game.id) });
|
|
833
918
|
return /* @__PURE__ */ jsx13(LoadoutScreen, { game }, game.id);
|
|
834
919
|
}
|
|
835
920
|
function hasLoadout(game) {
|
|
@@ -848,20 +933,22 @@ function useResumePoint(gameId) {
|
|
|
848
933
|
useEffect5(() => {
|
|
849
934
|
if (!gameId) return;
|
|
850
935
|
setLastActiveGame(gameId);
|
|
851
|
-
return
|
|
936
|
+
return () => {
|
|
937
|
+
if (AppState.currentState === "active") clearLastActiveGame();
|
|
938
|
+
};
|
|
852
939
|
}, [gameId, setLastActiveGame, clearLastActiveGame]);
|
|
853
940
|
}
|
|
854
941
|
|
|
855
942
|
// src/screens/HomeScreen.tsx
|
|
856
|
-
import { defaultColors as defaultColors2, useAutoPaperTheme as useAutoPaperTheme4 } from "@rific/auto-paper";
|
|
943
|
+
import { defaultColors as defaultColors2, FadingScrollView, useAutoPaperTheme as useAutoPaperTheme4 } from "@rific/auto-paper";
|
|
857
944
|
import { IconButton, Pressable } from "@rific/feedback-press";
|
|
858
945
|
import { FakeLandscapeView as FakeLandscapeView2, useRotation as useRotation4 } from "@tastic/core";
|
|
859
|
-
import { PressAwayOverlay as PressAwayOverlay2, usePopoverHost as usePopoverHost2, useWellColors, WELL_SHADOW } from "@tastic/hud";
|
|
860
|
-
import { ProfilePicker } from "@tastic/profile";
|
|
946
|
+
import { PressAwayOverlay as PressAwayOverlay2, usePopoverHost as usePopoverHost2, useWellColors, WELL_SHADOW } from "@tastic/hud/chrome";
|
|
947
|
+
import { getProfilePickerContentSize, ProfilePicker } from "@tastic/profile";
|
|
861
948
|
import { Redirect as Redirect2, router as router3, useIsFocused } from "expo-router";
|
|
862
|
-
import { useCallback as useCallback10, useEffect as useEffect6, useState as useState6 } from "react";
|
|
863
|
-
import { AppState
|
|
864
|
-
import { Icon, Text } from "react-native-paper";
|
|
949
|
+
import { useCallback as useCallback10, useEffect as useEffect6, useMemo as useMemo6, useState as useState6 } from "react";
|
|
950
|
+
import { AppState as AppState2, StyleSheet as StyleSheet4, View as View4 } from "react-native";
|
|
951
|
+
import { Icon, Portal, Text } from "react-native-paper";
|
|
865
952
|
|
|
866
953
|
// src/hooks/useFrozenWhileHidden.ts
|
|
867
954
|
import { useState as useState5 } from "react";
|
|
@@ -941,15 +1028,19 @@ function useHomeLayout() {
|
|
|
941
1028
|
}
|
|
942
1029
|
|
|
943
1030
|
// src/screens/HomeScreen.tsx
|
|
944
|
-
import { Fragment as
|
|
1031
|
+
import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
945
1032
|
function HomeScreen() {
|
|
946
1033
|
const pack = usePack();
|
|
947
1034
|
const { consumeResumeGame } = useLastActiveGame();
|
|
1035
|
+
const { autoShowPack } = useGameGuide();
|
|
948
1036
|
const [resumeGame] = useState6(() => {
|
|
949
1037
|
const id = consumeResumeGame();
|
|
950
1038
|
return pack.games.find((g) => g.id === id && g.resumable) ?? null;
|
|
951
1039
|
});
|
|
952
|
-
|
|
1040
|
+
useEffect6(() => {
|
|
1041
|
+
if (!resumeGame) autoShowPack();
|
|
1042
|
+
}, [resumeGame, autoShowPack]);
|
|
1043
|
+
if (resumeGame) return /* @__PURE__ */ jsx14(Redirect2, { href: playHref(resumeGame.id) });
|
|
953
1044
|
return /* @__PURE__ */ jsx14(HomeMenu, {});
|
|
954
1045
|
}
|
|
955
1046
|
function HomeMenu() {
|
|
@@ -957,13 +1048,14 @@ function HomeMenu() {
|
|
|
957
1048
|
const rotation = useRotation4();
|
|
958
1049
|
const layout = useHomeLayout();
|
|
959
1050
|
const { insets } = layout;
|
|
960
|
-
const { dark } = useAutoPaperTheme4();
|
|
961
|
-
const { openFirstTime, openFor } = useGameGuide();
|
|
1051
|
+
const { dark, colors } = useAutoPaperTheme4();
|
|
1052
|
+
const { openFirstTime, openFor, openPack } = useGameGuide();
|
|
962
1053
|
const [settingsOpen, setSettingsOpen] = useState6(false);
|
|
1054
|
+
const [rulesGame, setRulesGame] = useState6(null);
|
|
963
1055
|
const isFocused = useIsFocused();
|
|
964
|
-
const [appState, setAppState] = useState6(
|
|
1056
|
+
const [appState, setAppState] = useState6(AppState2.currentState);
|
|
965
1057
|
useEffect6(() => {
|
|
966
|
-
const subscription =
|
|
1058
|
+
const subscription = AppState2.addEventListener("change", setAppState);
|
|
967
1059
|
return () => subscription.remove();
|
|
968
1060
|
}, []);
|
|
969
1061
|
const heroActive = isFocused && appState === "active";
|
|
@@ -983,65 +1075,85 @@ function HomeMenu() {
|
|
|
983
1075
|
const closePicker = pickerHost.close;
|
|
984
1076
|
useEffect6(() => {
|
|
985
1077
|
closePicker();
|
|
986
|
-
}, [isWide, closePicker]);
|
|
987
|
-
const bg = dark ? "#000000" : "#FFFFFF";
|
|
988
|
-
const fg = dark ? "#FFFFFF" : "#000000";
|
|
989
|
-
const
|
|
990
|
-
const
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1078
|
+
}, [isWide, rotation, layout.windowWidth, layout.windowHeight, closePicker]);
|
|
1079
|
+
const bg = pack.Backdrop ? "transparent" : dark ? "#000000" : "#FFFFFF";
|
|
1080
|
+
const fg = pack.foreground ?? (dark ? "#FFFFFF" : "#000000");
|
|
1081
|
+
const { previewColor } = pack;
|
|
1082
|
+
const p1Drawn = previewColor ? previewColor(p1Color, dark) : p1Color;
|
|
1083
|
+
const triggerStyle = useMemo6(() => pack.Backdrop ? [styles4.pill, { backgroundColor: colors.surface }] : void 0, [pack.Backdrop, colors.surface]);
|
|
1084
|
+
const openGame = useCallback10((game) => openFirstTime(game.id, () => router3.push(game.useLoadout ? loadoutHref(game.id) : playHref(game.id))), [openFirstTime]);
|
|
1085
|
+
const openRules = useCallback10((game) => game.Rules ? setRulesGame(game) : openFor(game.id), [openFor]);
|
|
1086
|
+
const colorSection = { value: p1Color, onChange: handleP1ColorChange, swatches: defaultColors2, caption: selectedProfile ? pack.colorCaption : void 0, previewValue: previewColor ? (hex) => previewColor(hex, dark) : void 0 };
|
|
1087
|
+
const usePickerAlign = pack.usePickerAlign ?? noPickerAlign;
|
|
1088
|
+
const pickerSize = getProfilePickerContentSize(profiles.length, true, colorSection);
|
|
1089
|
+
const pickerAlign = usePickerAlign(pickerHost.openId === `${SEAT_ONE_PICKER}-profile`, pickerSize.width, pickerSize.height);
|
|
1090
|
+
const picker = /* @__PURE__ */ jsx14(ProfilePicker, { idPrefix: SEAT_ONE_PICKER, host: pickerHost, profiles, selectedId: lastSelected[1], color: p1Drawn, dark, guestLabel: pack.guestLabel ?? "P1", nullLabel: pack.nullLabel, onSelect: (profile) => selectProfile(1, profile?.id ?? null), onManage: () => router3.push(PROFILES_HREF), colorSection, alignOverride: pickerAlign, triggerStyle });
|
|
1091
|
+
const pressAway = /* @__PURE__ */ jsx14(PressAwayOverlay2, { active: pickerHost.openId !== null, onPress: pickerHost.close, style: styles4.pressAway });
|
|
1092
|
+
const RulesSheet = rulesGame?.Rules;
|
|
1093
|
+
return /* @__PURE__ */ jsxs6(View4, { style: [styles4.container, { backgroundColor: bg }], children: [
|
|
1094
|
+
/* @__PURE__ */ jsx14(FakeLandscapeView2, { style: styles4.rotatable, children: /* @__PURE__ */ jsxs6(RulesHost, { enabled: pack.games.some((game) => game.Rules), children: [
|
|
1095
|
+
isWide ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
1096
|
+
pressAway,
|
|
1097
|
+
/* @__PURE__ */ jsx14(FadingScrollView, { style: [styles4.wideRightPane, { left: layout.paddingLeft + layout.leftPaneWidth + HOME_WIDE_PANE_GAP }], contentContainerStyle: { paddingTop: 16 + insets.top, paddingBottom: 16 + insets.bottom, paddingRight: layout.paddingRight }, children: /* @__PURE__ */ jsx14(View4, { style: [styles4.grid, { width: layout.rightPaneWidth }], children: pack.games.map((game) => /* @__PURE__ */ jsx14(GameTile, { game, width: layout.tileWidth, previewWidth: layout.previewWidth, previewRevealed: previewsRevealed, dark, p1Color, p2Color, onPress: () => openGame(game), onRules: hasRules(game) ? () => openRules(game) : void 0 }, game.id)) }) }),
|
|
1098
|
+
/* @__PURE__ */ jsxs6(View4, { style: [styles4.wideLeftPane, { left: layout.paddingLeft, width: layout.leftPaneWidth, paddingTop: HOME_TOP_BAND + insets.top, paddingBottom: 16 + insets.bottom }, pickerHost.openId !== null && styles4.pickerOpen], children: [
|
|
997
1099
|
/* @__PURE__ */ jsx14(pack.HeroTitle, { words: pack.wordmark, color: fg, p1Color, p2Color, active: heroActive, fit: layout.heroFit }),
|
|
998
|
-
/* @__PURE__ */ jsx14(
|
|
1100
|
+
/* @__PURE__ */ jsx14(View4, { style: styles4.widePicker, children: picker })
|
|
999
1101
|
] })
|
|
1000
|
-
] }) : /* @__PURE__ */ jsxs6(
|
|
1102
|
+
] }) : /* @__PURE__ */ jsxs6(FadingScrollView, { style: styles4.scroll, contentContainerStyle: [styles4.content, { paddingTop: 56 + insets.top, paddingBottom: 24 + insets.bottom, paddingLeft: 16 + insets.left, paddingRight: 16 + insets.right }], children: [
|
|
1103
|
+
pressAway,
|
|
1001
1104
|
/* @__PURE__ */ jsx14(pack.HeroTitle, { words: pack.wordmark, color: fg, p1Color, p2Color, active: heroActive }),
|
|
1002
|
-
/* @__PURE__ */ jsx14(
|
|
1003
|
-
/* @__PURE__ */ jsx14(
|
|
1105
|
+
/* @__PURE__ */ jsx14(View4, { style: [styles4.picker, pickerHost.openId !== null && styles4.pickerOpen], children: picker }),
|
|
1106
|
+
/* @__PURE__ */ jsx14(View4, { style: styles4.list, children: pack.games.map((game) => /* @__PURE__ */ jsx14(GameRow, { game, dark, p1Color, p2Color, previewRevealed: previewsRevealed, onPress: () => openGame(game), onRules: hasRules(game) ? () => openRules(game) : void 0 }, game.id)) })
|
|
1004
1107
|
] }),
|
|
1005
|
-
/* @__PURE__ */ jsx14(IconButton, { icon: "trophy", iconColor: fg, size: 24, style: [styles4.corner, { top: 8 + insets.top, left: 8 + insets.left }], onPress: () => router3.push(
|
|
1006
|
-
/* @__PURE__ */ jsx14(IconButton, { icon: "cog", iconColor: fg, size: 24, style: [styles4.corner, { top: 8 + insets.top, right: 8 + insets.right }], onPress: () => setSettingsOpen(true), accessibilityLabel: "Settings" })
|
|
1007
|
-
|
|
1008
|
-
|
|
1108
|
+
/* @__PURE__ */ jsx14(IconButton, { icon: "trophy", iconColor: fg, size: 24, style: [styles4.corner, { top: 8 + insets.top, left: 8 + insets.left }], onPress: () => router3.push(STATS_HREF), accessibilityLabel: "Stats & Achievements" }),
|
|
1109
|
+
/* @__PURE__ */ jsx14(IconButton, { icon: "cog", iconColor: fg, size: 24, style: [styles4.corner, { top: 8 + insets.top, right: 8 + insets.right }], onPress: () => setSettingsOpen(true), accessibilityLabel: "Settings" }),
|
|
1110
|
+
RulesSheet && /* @__PURE__ */ jsx14(RulesSheet, { visible: true, onDismiss: () => setRulesGame(null) })
|
|
1111
|
+
] }) }),
|
|
1112
|
+
/* @__PURE__ */ jsx14(SettingsDialog, { visible: settingsOpen, onDismiss: () => setSettingsOpen(false), rotation, onShowHowToPlay: pack.guide ? openPack : void 0 })
|
|
1009
1113
|
] });
|
|
1010
1114
|
}
|
|
1115
|
+
var SEAT_ONE_PICKER = "home-p1";
|
|
1116
|
+
var noPickerAlign = () => void 0;
|
|
1117
|
+
function hasRules(game) {
|
|
1118
|
+
return !!(game.Rules || game.guide);
|
|
1119
|
+
}
|
|
1120
|
+
function RulesHost({ enabled, children }) {
|
|
1121
|
+
return enabled ? /* @__PURE__ */ jsx14(Portal.Host, { children }) : /* @__PURE__ */ jsx14(Fragment4, { children });
|
|
1122
|
+
}
|
|
1011
1123
|
function GameRow({ game, dark, p1Color, p2Color, previewRevealed, onPress, onRules }) {
|
|
1012
1124
|
const well = useWellColors();
|
|
1013
1125
|
const GameIcon = game.icon;
|
|
1014
1126
|
return (
|
|
1015
1127
|
// The rules button sits beside the row's own button, laid over its end, not inside it: a button
|
|
1016
1128
|
// inside a button is invalid on the web.
|
|
1017
|
-
/* @__PURE__ */ jsxs6(
|
|
1129
|
+
/* @__PURE__ */ jsxs6(View4, { children: [
|
|
1018
1130
|
/* @__PURE__ */ jsxs6(Pressable, { onPress, style: [styles4.row, { backgroundColor: well.bg }], accessibilityRole: "button", accessibilityLabel: game.title, children: [
|
|
1019
|
-
game.Preview ? /* @__PURE__ */ jsx14(PreviewBox, { game, width: ROW_PREVIEW_WIDTH, revealed: previewRevealed, dark, p1Color, p2Color }) : /* @__PURE__ */ jsx14(
|
|
1020
|
-
/* @__PURE__ */ jsxs6(
|
|
1131
|
+
game.Preview ? /* @__PURE__ */ jsx14(PreviewBox, { game, width: ROW_PREVIEW_WIDTH, revealed: previewRevealed, dark, p1Color, p2Color }) : /* @__PURE__ */ jsx14(View4, { style: [styles4.rowIcon, { backgroundColor: dark ? ICON_DARK_BG : ICON_LIGHT_BG }], children: GameIcon && /* @__PURE__ */ jsx14(GameIcon, { size: ROW_ICON_SIZE, p1Color, p2Color, dark }) }),
|
|
1132
|
+
/* @__PURE__ */ jsxs6(View4, { style: styles4.rowText, children: [
|
|
1021
1133
|
/* @__PURE__ */ jsx14(Text, { variant: "titleLarge", style: [styles4.rowTitle, { color: well.fg }], numberOfLines: 1, children: game.title }),
|
|
1022
1134
|
/* @__PURE__ */ jsx14(Text, { variant: "bodyMedium", style: { color: well.fgMuted }, numberOfLines: 2, children: game.blurb })
|
|
1023
1135
|
] })
|
|
1024
1136
|
] }),
|
|
1025
|
-
/* @__PURE__ */ jsx14(Pressable, { onPress: onRules, style: styles4.rulesButton, hitSlop: 12, accessibilityRole: "button", accessibilityLabel: `${game.title} rules`, children: /* @__PURE__ */ jsx14(Icon, { source: "information-outline", size: 22, color: well.fgMuted }) })
|
|
1137
|
+
onRules && /* @__PURE__ */ jsx14(Pressable, { onPress: onRules, style: styles4.rulesButton, hitSlop: 12, accessibilityRole: "button", accessibilityLabel: `${game.title} rules`, children: /* @__PURE__ */ jsx14(Icon, { source: "information-outline", size: 22, color: well.fgMuted }) })
|
|
1026
1138
|
] })
|
|
1027
1139
|
);
|
|
1028
1140
|
}
|
|
1029
1141
|
function GameTile({ game, width, previewWidth, dark, p1Color, p2Color, previewRevealed, onPress, onRules }) {
|
|
1030
1142
|
const well = useWellColors();
|
|
1031
|
-
return /* @__PURE__ */ jsxs6(
|
|
1143
|
+
return /* @__PURE__ */ jsxs6(View4, { style: { width }, children: [
|
|
1032
1144
|
/* @__PURE__ */ jsxs6(Pressable, { onPress, style: [styles4.tile, { backgroundColor: well.bg }], accessibilityRole: "button", accessibilityLabel: game.title, children: [
|
|
1033
1145
|
/* @__PURE__ */ jsx14(PreviewBox, { game, width: previewWidth, revealed: previewRevealed, dark, p1Color, p2Color }),
|
|
1034
1146
|
/* @__PURE__ */ jsx14(Text, { variant: "titleMedium", style: [styles4.rowTitle, { color: well.fg }], numberOfLines: 1, children: game.title }),
|
|
1035
1147
|
/* @__PURE__ */ jsx14(Text, { variant: "bodySmall", style: { color: well.fgMuted }, numberOfLines: 3, children: game.blurb })
|
|
1036
1148
|
] }),
|
|
1037
|
-
/* @__PURE__ */ jsx14(Pressable, { onPress: onRules, style: styles4.tileRulesButton, hitSlop: 12, accessibilityRole: "button", accessibilityLabel: `${game.title} rules`, children: /* @__PURE__ */ jsx14(Icon, { source: "information-outline", size: 20, color: "#FFFFFF" }) })
|
|
1149
|
+
onRules && /* @__PURE__ */ jsx14(Pressable, { onPress: onRules, style: styles4.tileRulesButton, hitSlop: 12, accessibilityRole: "button", accessibilityLabel: `${game.title} rules`, children: /* @__PURE__ */ jsx14(Icon, { source: "information-outline", size: 20, color: "#FFFFFF" }) })
|
|
1038
1150
|
] });
|
|
1039
1151
|
}
|
|
1040
1152
|
function PreviewBox({ game, width, revealed, dark, p1Color, p2Color }) {
|
|
1041
1153
|
const height = Math.round(width / PREVIEW_ASPECT);
|
|
1042
1154
|
const { Preview, icon: GameIcon } = game;
|
|
1043
1155
|
const iconSize = Math.min(height, ROW_ICON_SIZE);
|
|
1044
|
-
return /* @__PURE__ */ jsx14(
|
|
1156
|
+
return /* @__PURE__ */ jsx14(View4, { style: [styles4.preview, { width, height, backgroundColor: dark ? ICON_DARK_BG : ICON_LIGHT_BG }], children: Preview ? revealed && /* @__PURE__ */ jsx14(Preview, { width, height, p1Color, p2Color, dark }) : GameIcon && /* @__PURE__ */ jsx14(View4, { style: [styles4.previewIcon, { width: iconSize, height: iconSize, borderRadius: iconSize * 0.22 }], children: /* @__PURE__ */ jsx14(GameIcon, { size: iconSize, p1Color, p2Color, dark }) }) });
|
|
1045
1157
|
}
|
|
1046
1158
|
var ROW_ICON_SIZE = 56;
|
|
1047
1159
|
var ICON_LIGHT_BG = "#FAFAFA";
|
|
@@ -1076,6 +1188,15 @@ var styles4 = StyleSheet4.create({
|
|
|
1076
1188
|
pickerOpen: {
|
|
1077
1189
|
zIndex: 100
|
|
1078
1190
|
},
|
|
1191
|
+
// Seat 1's button on the theme's surface (over a backdrop), the same pill as the tiles' wells.
|
|
1192
|
+
pill: {
|
|
1193
|
+
borderRadius: 999
|
|
1194
|
+
},
|
|
1195
|
+
// Over the rows (and, wide, the grid), whose own stacking is unset, so a press on them reaches it on
|
|
1196
|
+
// the web too, and under the raised button and left pane, so the menu's rows stay pressable.
|
|
1197
|
+
pressAway: {
|
|
1198
|
+
zIndex: 5
|
|
1199
|
+
},
|
|
1079
1200
|
// The preview's own rounding, clipping whatever it draws to the box.
|
|
1080
1201
|
preview: {
|
|
1081
1202
|
alignItems: "center",
|
|
@@ -1166,66 +1287,109 @@ var styles4 = StyleSheet4.create({
|
|
|
1166
1287
|
|
|
1167
1288
|
// src/screens/ProfilesRoute.tsx
|
|
1168
1289
|
import { defaultColors as defaultColors3 } from "@rific/auto-paper";
|
|
1290
|
+
import { RotatedFrame as RotatedFrame2 } from "@tastic/hud/chrome";
|
|
1169
1291
|
import { ProfilesScreen } from "@tastic/profile";
|
|
1170
1292
|
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
1293
|
+
var ROSTER_MAX_WIDTH = 640;
|
|
1294
|
+
function useRosterAlign(open, contentWidth, contentHeight) {
|
|
1295
|
+
const { usePickerAlign } = usePack();
|
|
1296
|
+
return usePickerAlign(open, contentWidth, contentHeight, { maxContentWidth: ROSTER_MAX_WIDTH });
|
|
1297
|
+
}
|
|
1171
1298
|
function ProfilesRoute() {
|
|
1172
1299
|
const { profiles, createProfile, updateProfile, deleteProfile } = useProfiles();
|
|
1173
|
-
|
|
1300
|
+
const { Backdrop, foreground, newProfileColor, usePickerAlign } = usePack();
|
|
1301
|
+
return /* @__PURE__ */ jsx15(RotatedFrame2, { rotatedSafeArea: true, portalHost: true, maxContentWidth: ROSTER_MAX_WIDTH, children: /* @__PURE__ */ jsx15(ProfilesScreen, { profiles, defaultColor: newProfileColor ?? defaultColors3[0].value, onCreate: (patch) => createProfile(patch), onSave: (id, patch) => updateProfile(id, patch), onDelete: deleteProfile, onBack: safeBack, fg: foreground, bg: Backdrop ? "transparent" : void 0, useColorPickerAlign: usePickerAlign ? useRosterAlign : void 0 }) });
|
|
1174
1302
|
}
|
|
1175
1303
|
|
|
1176
1304
|
// src/screens/StatsScreen.tsx
|
|
1177
1305
|
import { useAutoPaperTheme as useAutoPaperTheme5 } from "@rific/auto-paper";
|
|
1178
|
-
import {
|
|
1179
|
-
import {
|
|
1180
|
-
import {
|
|
1181
|
-
import {
|
|
1182
|
-
import { StyleSheet as StyleSheet5, View as View4 } from "react-native";
|
|
1306
|
+
import { BaseStatsScreen, PressAwayOverlay as PressAwayOverlay3, RotatedFrame as RotatedFrame3, usePopoverHost as usePopoverHost3 } from "@tastic/hud/chrome";
|
|
1307
|
+
import { getOptionPickerContentSize as getOptionPickerContentSize2, OptionPicker as OptionPicker2 } from "@tastic/profile";
|
|
1308
|
+
import { useMemo as useMemo7, useState as useState7 } from "react";
|
|
1309
|
+
import { StyleSheet as StyleSheet5, View as View5 } from "react-native";
|
|
1183
1310
|
import { SegmentedButtons, Text as Text2 } from "react-native-paper";
|
|
1184
1311
|
import { useSafeAreaInsets as useSafeAreaInsets3 } from "react-native-safe-area-context";
|
|
1185
1312
|
import { useDispatch as useDispatch10 } from "react-redux";
|
|
1186
1313
|
import { jsx as jsx16, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1314
|
+
var STATS = "stats";
|
|
1315
|
+
var ACHIEVEMENTS = "achievements";
|
|
1187
1316
|
var TABS = [
|
|
1188
|
-
{ value:
|
|
1189
|
-
{ value:
|
|
1317
|
+
{ value: STATS, label: "Stats", icon: "chart-box-outline" },
|
|
1318
|
+
{ value: ACHIEVEMENTS, label: "Achievements", icon: "trophy-outline" }
|
|
1190
1319
|
];
|
|
1320
|
+
var LABEL_WIDE_TAB = { flexBasis: "auto", flexGrow: 1, flexShrink: 1 };
|
|
1321
|
+
var NO_PACK_TABS = [];
|
|
1191
1322
|
var EVERYONE = "__everyone__";
|
|
1192
1323
|
var ALL_GAMES = "__all__";
|
|
1193
1324
|
var PLAYER_PICKER_ID = "stats-player";
|
|
1194
1325
|
var GAME_PICKER_ID = "stats-game";
|
|
1326
|
+
var STATS_MAX_WIDTH = 640;
|
|
1195
1327
|
function StatsScreen() {
|
|
1328
|
+
const pickerHost = usePopoverHost3();
|
|
1329
|
+
return /* @__PURE__ */ jsx16(StatsPressAway, { host: pickerHost, children: /* @__PURE__ */ jsx16(RotatedFrame3, { rotatedSafeArea: true, portalHost: true, maxContentWidth: STATS_MAX_WIDTH, children: /* @__PURE__ */ jsx16(StatsContent, { pickerHost }) }) });
|
|
1330
|
+
}
|
|
1331
|
+
function StatsPressAway({ host, children }) {
|
|
1332
|
+
return /* @__PURE__ */ jsx16(View5, { testID: "stats-press-away", style: styles5.screen, onStartShouldSetResponder: () => host.openId !== null, onResponderRelease: host.close, children });
|
|
1333
|
+
}
|
|
1334
|
+
var useNoPackReset = () => null;
|
|
1335
|
+
var noPickerAlign2 = () => void 0;
|
|
1336
|
+
function StatsContent({ pickerHost }) {
|
|
1196
1337
|
const pack = usePack();
|
|
1197
1338
|
const { dark, colors } = useAutoPaperTheme5();
|
|
1198
|
-
const
|
|
1199
|
-
const
|
|
1200
|
-
const { profiles } = useProfiles();
|
|
1339
|
+
const insets = useSafeAreaInsets3();
|
|
1340
|
+
const { profiles, lastSelected } = useProfiles();
|
|
1201
1341
|
const dispatch = useDispatch10();
|
|
1202
|
-
const
|
|
1203
|
-
const [tab, setTab] = useState7(
|
|
1342
|
+
const fg = pack.foreground ?? colors.onBackground;
|
|
1343
|
+
const [tab, setTab] = useState7(STATS);
|
|
1204
1344
|
const [gameId, setGameId] = useState7(ALL_GAMES);
|
|
1205
|
-
const [selectedProfileId, setSelectedProfileId] = useState7(null);
|
|
1206
|
-
const profileId = selectedProfileId && profiles.some((p) => p.id === selectedProfileId) ? selectedProfileId : null;
|
|
1345
|
+
const [selectedProfileId, setSelectedProfileId] = useState7(() => pack.guestStats ? lastSelected[1] ?? GUEST_PLAYER_ID : null);
|
|
1346
|
+
const profileId = selectedProfileId && profiles.length > 0 && (pack.guestStats && selectedProfileId === GUEST_PLAYER_ID || profiles.some((p) => p.id === selectedProfileId)) ? selectedProfileId : null;
|
|
1207
1347
|
const games = gameId === ALL_GAMES ? pack.games : pack.games.filter((g) => g.id === gameId);
|
|
1208
|
-
const
|
|
1209
|
-
const
|
|
1210
|
-
const
|
|
1211
|
-
const
|
|
1212
|
-
|
|
1348
|
+
const packTabs = pack.statsTabs ?? NO_PACK_TABS;
|
|
1349
|
+
const packTab = packTabs.find((t) => t.value === tab);
|
|
1350
|
+
const showGamePicker = !(tab === ACHIEVEMENTS && pack.AchievementList);
|
|
1351
|
+
const playerOptions = useMemo7(() => [{ value: EVERYONE, label: "Everyone", icon: "account-group" }, ...pack.guestStats ? [{ value: GUEST_PLAYER_ID, label: "Guest", icon: "account-outline" }] : [], ...profiles.map((p) => ({ value: p.id, label: p.name, icon: "account", color: pack.previewColor ? pack.previewColor(p.color, dark) : p.color }))], [profiles, pack, dark]);
|
|
1352
|
+
const gameOptions = useMemo7(() => [{ value: ALL_GAMES, label: "All Games", icon: "view-grid-outline" }, ...pack.games.map((g) => ({ value: g.id, label: g.title }))], [pack.games]);
|
|
1353
|
+
const tabButtons = useMemo7(() => packTabs.length > 0 ? [{ value: STATS, label: "Stats" }, ...packTabs.map(({ value, label }) => ({ value, label })), { value: ACHIEVEMENTS, label: "Achievements" }].map((button) => ({ ...button, style: LABEL_WIDE_TAB })) : TABS, [packTabs]);
|
|
1354
|
+
const title = tab === STATS ? "Stats" : tab === ACHIEVEMENTS ? "Achievements" : packTab?.label ?? "";
|
|
1355
|
+
const usePackReset = pack.resetStats?.useReset ?? useNoPackReset;
|
|
1356
|
+
const packReset = usePackReset();
|
|
1357
|
+
const reset = resetFor(pack, packReset, profileId, profiles, games, () => games.forEach((g) => g.resetStats && dispatch(g.resetStats())));
|
|
1358
|
+
const usePickerAlign = pack.usePickerAlign ?? noPickerAlign2;
|
|
1359
|
+
const playerSize = getOptionPickerContentSize2(playerOptions.length);
|
|
1360
|
+
const gameSize = getOptionPickerContentSize2(gameOptions.length);
|
|
1361
|
+
const playerAlign = usePickerAlign(pickerHost.openId === PLAYER_PICKER_ID, playerSize.width, playerSize.height);
|
|
1362
|
+
const gameAlign = usePickerAlign(pickerHost.openId === GAME_PICKER_ID, gameSize.width, gameSize.height);
|
|
1363
|
+
const surface = pack.Backdrop ? { backgroundColor: colors.surface } : void 0;
|
|
1364
|
+
const triggerStyle = pack.Backdrop ? [styles5.pill, surface] : void 0;
|
|
1365
|
+
const PackTabContent = packTab?.Content;
|
|
1366
|
+
return /* @__PURE__ */ jsxs7(BaseStatsScreen, { title, onBack: safeBack, insets, onReset: reset.onReset, resetLabel: reset.label, resetConfirmTitle: reset.title, resetConfirmBody: reset.body, fg: pack.foreground, bg: pack.Backdrop ? "transparent" : void 0, rotation: 0, children: [
|
|
1213
1367
|
/* @__PURE__ */ jsx16(PressAwayOverlay3, { active: pickerHost.openId !== null, onPress: pickerHost.close, style: styles5.pressAway }),
|
|
1214
|
-
/* @__PURE__ */ jsx16(SegmentedButtons, { value: tab, onValueChange:
|
|
1215
|
-
/* @__PURE__ */ jsxs7(
|
|
1216
|
-
profiles.length > 0 && /* @__PURE__ */ jsx16(OptionPicker2, { id: PLAYER_PICKER_ID, host: pickerHost, options: playerOptions, value: profileId ?? EVERYONE, onChange: (value) => setSelectedProfileId(value === EVERYONE ? null : value), color: colors.primary, dark, icon: profileId ? "account" : "account-group" }),
|
|
1217
|
-
/* @__PURE__ */ jsx16(OptionPicker2, { id: GAME_PICKER_ID, host: pickerHost, options: gameOptions, value: gameId, onChange: setGameId, color: colors.primary, dark, icon: "view-grid-outline" })
|
|
1368
|
+
/* @__PURE__ */ jsx16(SegmentedButtons, { value: tab, onValueChange: setTab, buttons: tabButtons, style: [styles5.tabs, surface] }),
|
|
1369
|
+
/* @__PURE__ */ jsxs7(View5, { style: [styles5.filters, pickerHost.openId !== null && styles5.filtersOpen], onStartShouldSetResponder: () => pickerHost.openId !== null, children: [
|
|
1370
|
+
profiles.length > 0 && /* @__PURE__ */ jsx16(OptionPicker2, { id: PLAYER_PICKER_ID, host: pickerHost, options: playerOptions, value: profileId ?? EVERYONE, onChange: (value) => setSelectedProfileId(value === EVERYONE ? null : value), color: colors.primary, dark, icon: profileId ? "account" : "account-group", alignOverride: playerAlign, triggerStyle }),
|
|
1371
|
+
showGamePicker && /* @__PURE__ */ jsx16(OptionPicker2, { id: GAME_PICKER_ID, host: pickerHost, options: gameOptions, value: gameId, onChange: setGameId, color: colors.primary, dark, icon: "view-grid-outline", alignOverride: gameAlign, triggerStyle })
|
|
1218
1372
|
] }),
|
|
1219
|
-
tab ===
|
|
1220
|
-
|
|
1221
|
-
|
|
1373
|
+
tab === STATS && pack.StatsSections && /* @__PURE__ */ jsx16(pack.StatsSections, { profileId, gameId: gameId === ALL_GAMES ? null : gameId }),
|
|
1374
|
+
PackTabContent && /* @__PURE__ */ jsx16(PackTabContent, { profileId, gameId: gameId === ALL_GAMES ? null : gameId }),
|
|
1375
|
+
tab === ACHIEVEMENTS && pack.AchievementList ? /* @__PURE__ */ jsx16(pack.AchievementList, { profileId }) : (tab === STATS || tab === ACHIEVEMENTS) && games.map((game) => /* @__PURE__ */ jsx16(GameStats, { game, tab, profileId, showTitle: games.length > 1 && pack.statsGameTitles !== false, titleColor: fg }, game.id))
|
|
1376
|
+
] });
|
|
1222
1377
|
}
|
|
1223
|
-
function
|
|
1224
|
-
|
|
1378
|
+
function resetFor(pack, packReset, profileId, profiles, games, resetGames) {
|
|
1379
|
+
if (pack.resetStats && packReset) {
|
|
1380
|
+
const name = profileId === GUEST_PLAYER_ID ? "Guest" : profileId ? profiles.find((p) => p.id === profileId)?.name ?? null : null;
|
|
1381
|
+
return { onReset: () => packReset(profileId), ...pack.resetStats.labels(name) };
|
|
1382
|
+
}
|
|
1383
|
+
const scope = games.length === 1 ? games[0].title : "every game";
|
|
1384
|
+
return { onReset: resetGames, label: games.length === 1 ? `Reset ${scope} Stats` : "Reset All Stats", title: "Reset Stats?", body: `Erases every player's stats and achievements in ${scope}. This cannot be undone.` };
|
|
1385
|
+
}
|
|
1386
|
+
function GameStats({ game, tab, profileId, showTitle, titleColor }) {
|
|
1225
1387
|
const { StatsSections, AchievementList } = game;
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1388
|
+
const Sections = tab === STATS ? StatsSections : AchievementList;
|
|
1389
|
+
if (!Sections) return null;
|
|
1390
|
+
return /* @__PURE__ */ jsxs7(View5, { style: styles5.game, children: [
|
|
1391
|
+
showTitle && /* @__PURE__ */ jsx16(Text2, { variant: "titleLarge", style: [styles5.gameTitle, { color: titleColor }], children: game.title }),
|
|
1392
|
+
/* @__PURE__ */ jsx16(Sections, { profileId })
|
|
1229
1393
|
] });
|
|
1230
1394
|
}
|
|
1231
1395
|
var styles5 = StyleSheet5.create({
|
|
@@ -1245,14 +1409,17 @@ var styles5 = StyleSheet5.create({
|
|
|
1245
1409
|
fontWeight: "bold",
|
|
1246
1410
|
marginTop: 8
|
|
1247
1411
|
},
|
|
1412
|
+
// A picker's button on the theme's surface (over a backdrop), the same pill as Home's.
|
|
1413
|
+
pill: {
|
|
1414
|
+
borderRadius: 999
|
|
1415
|
+
},
|
|
1248
1416
|
pressAway: {
|
|
1249
1417
|
zIndex: 50
|
|
1250
1418
|
},
|
|
1251
|
-
|
|
1252
|
-
rotatable: {
|
|
1419
|
+
screen: {
|
|
1253
1420
|
flex: 1
|
|
1254
1421
|
},
|
|
1255
|
-
// SegmentedButtons' own rounding.
|
|
1422
|
+
// SegmentedButtons' own rounding, so a surface behind it is the same pill.
|
|
1256
1423
|
tabs: {
|
|
1257
1424
|
borderRadius: 20
|
|
1258
1425
|
}
|
|
@@ -1288,7 +1455,7 @@ import { defaultSoundSettings, hapticReducer, soundReducer } from "@rific/feedba
|
|
|
1288
1455
|
import { profilesReducer } from "@tastic/profile";
|
|
1289
1456
|
var initialSoundSettings = { ...defaultSoundSettings, enabled: !__DEV__ };
|
|
1290
1457
|
var appSoundReducer = (state = initialSoundSettings, action) => soundReducer(state, action);
|
|
1291
|
-
function createShellReducers({ seatColors, appearance = "system" }) {
|
|
1458
|
+
function createShellReducers({ seatColors, appearance = "system", seatTheme }) {
|
|
1292
1459
|
return {
|
|
1293
1460
|
// Explicit primary/secondary triad (rather than a single seed expanded via harmony) so the pack's
|
|
1294
1461
|
// identity is pinned exactly instead of drifting with whatever hue math a harmony offset would
|
|
@@ -1297,7 +1464,9 @@ function createShellReducers({ seatColors, appearance = "system" }) {
|
|
|
1297
1464
|
// derived, not picked, via getThirdColor: the hue maximally distant from both (see
|
|
1298
1465
|
// @rific/auto-paper's README "Explicit triad" section). The appearance is the pack's ('system',
|
|
1299
1466
|
// createThemeReducer's own default, when it sets none).
|
|
1300
|
-
|
|
1467
|
+
// A pack with its own seatTheme starts from what seat 1's default color makes of it instead (dark
|
|
1468
|
+
// only when the pack's appearance is; hooks/useSeatColors.ts re-applies it with the live mode).
|
|
1469
|
+
theme: createThemeReducer(seatTheme ? { appearance, ...seatTheme(seatColors, appearance === "dark") } : { appearance, color: { primary: seatColors[1], secondary: seatColors[2], tertiary: getThirdColor2(seatColors[1], seatColors[2]) } }),
|
|
1301
1470
|
haptic: hapticReducer,
|
|
1302
1471
|
sound: appSoundReducer,
|
|
1303
1472
|
// @rific/core's Keep Awake setting (see Providers.tsx's KeepAwakeBridge), under the namespace its
|
|
@@ -1325,7 +1494,7 @@ var errorMiddleware = () => (next) => (action) => {
|
|
|
1325
1494
|
if (!hasError(action)) return next(action);
|
|
1326
1495
|
return action;
|
|
1327
1496
|
};
|
|
1328
|
-
function createPackStore(pack, { keyPrefix, migrate }) {
|
|
1497
|
+
function createPackStore(pack, { keyPrefix, migrate, blacklist = [], serializableIgnoredPaths = [], middleware = [] }) {
|
|
1329
1498
|
const gameReducers = Object.assign({}, ...pack.games.map((game) => game.reducers));
|
|
1330
1499
|
const resumeReducers = pack.games.some((game) => game.resumable) ? { lastActiveGame: lastActiveGameSlice_default } : {};
|
|
1331
1500
|
const rootReducer = combineReducers({ ...createShellReducers(pack), ...resumeReducers, ...pack.reducers, ...gameReducers });
|
|
@@ -1343,7 +1512,7 @@ function createPackStore(pack, { keyPrefix, migrate }) {
|
|
|
1343
1512
|
// failing to ever resolve at all isn't a failure mode worth a 5s failsafe for.
|
|
1344
1513
|
timeout: 0,
|
|
1345
1514
|
// liveplay is per-round state that must never survive a relaunch (see liveplaySlice.ts).
|
|
1346
|
-
blacklist: ["liveplay"],
|
|
1515
|
+
blacklist: ["liveplay", ...blacklist],
|
|
1347
1516
|
migrate
|
|
1348
1517
|
},
|
|
1349
1518
|
rootReducer
|
|
@@ -1353,10 +1522,11 @@ function createPackStore(pack, { keyPrefix, migrate }) {
|
|
|
1353
1522
|
const defaultMiddleware = getDefaultMiddleware({
|
|
1354
1523
|
immutableCheck: false,
|
|
1355
1524
|
serializableCheck: {
|
|
1356
|
-
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
|
|
1525
|
+
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
|
|
1526
|
+
ignoredPaths: [...serializableIgnoredPaths]
|
|
1357
1527
|
}
|
|
1358
1528
|
});
|
|
1359
|
-
return defaultMiddleware.concat(errorMiddleware);
|
|
1529
|
+
return defaultMiddleware.concat(errorMiddleware, ...middleware);
|
|
1360
1530
|
},
|
|
1361
1531
|
reducer: persistedReducer
|
|
1362
1532
|
});
|
|
@@ -1407,7 +1577,7 @@ function forgetProfileAchievements(dropStats) {
|
|
|
1407
1577
|
|
|
1408
1578
|
// src/gameStats.tsx
|
|
1409
1579
|
import { createAchievementsSlice, mapUnlocksBySeat, useAchievements } from "@tastic/achievements";
|
|
1410
|
-
import { createContext as createContext5, useCallback as useCallback11, useContext as useContext5, useMemo as
|
|
1580
|
+
import { createContext as createContext5, useCallback as useCallback11, useContext as useContext5, useMemo as useMemo8 } from "react";
|
|
1411
1581
|
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
1412
1582
|
var SEATS = [1, 2];
|
|
1413
1583
|
function createGameStats({ key, defaultStats, isValidStats, catalog, getProfileStatsView }) {
|
|
@@ -1440,7 +1610,7 @@ function createGameStats({ key, defaultStats, isValidStats, catalog, getProfileS
|
|
|
1440
1610
|
},
|
|
1441
1611
|
[recordOutcome]
|
|
1442
1612
|
);
|
|
1443
|
-
const value =
|
|
1613
|
+
const value = useMemo8(() => ({ stats, unlockedAchievements, record, resetAll }), [stats, unlockedAchievements, record, resetAll]);
|
|
1444
1614
|
return /* @__PURE__ */ jsx18(Context.Provider, { value, children });
|
|
1445
1615
|
}
|
|
1446
1616
|
function useStats() {
|
|
@@ -1452,7 +1622,7 @@ function createGameStats({ key, defaultStats, isValidStats, catalog, getProfileS
|
|
|
1452
1622
|
}
|
|
1453
1623
|
|
|
1454
1624
|
// src/playerSettings.ts
|
|
1455
|
-
import { useCallback as useCallback12, useMemo as
|
|
1625
|
+
import { useCallback as useCallback12, useMemo as useMemo9 } from "react";
|
|
1456
1626
|
import { useDispatch as useDispatch11, useSelector as useSelector9 } from "react-redux";
|
|
1457
1627
|
var SEATS2 = [1, 2];
|
|
1458
1628
|
function seatPlayerKey(selection, seat) {
|
|
@@ -1496,7 +1666,7 @@ function createPlayerSettings({ id, defaults, distinctControls }) {
|
|
|
1496
1666
|
const saved = useSelector9((state) => state[settingsKey]);
|
|
1497
1667
|
const controls = useSelector9((state) => state[controlsKey]);
|
|
1498
1668
|
const dispatch = useDispatch11();
|
|
1499
|
-
const settings =
|
|
1669
|
+
const settings = useMemo9(() => compose(saved, controls, selection, seatTwoIsPerson), [saved, controls, selection, seatTwoIsPerson]);
|
|
1500
1670
|
const setSettings = useCallback12(
|
|
1501
1671
|
(next) => {
|
|
1502
1672
|
const { controlScheme, ...changes } = next;
|
|
@@ -1516,6 +1686,7 @@ function createPlayerSettings({ id, defaults, distinctControls }) {
|
|
|
1516
1686
|
export {
|
|
1517
1687
|
DEFAULT_APP_SETTINGS,
|
|
1518
1688
|
DEFAULT_OPPONENT,
|
|
1689
|
+
GUEST_PLAYER_ID,
|
|
1519
1690
|
HomeScreen,
|
|
1520
1691
|
LoadoutRoute,
|
|
1521
1692
|
PREVIEW_ASPECT,
|