@tastic/hud 0.14.1 → 0.15.0
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 +110 -79
- package/dist/chrome/index.d.mts +239 -0
- package/dist/chrome/index.d.ts +239 -0
- package/dist/chrome/index.js +772 -0
- package/dist/chrome/index.mjs +44 -0
- package/dist/chunk-OTMMST5M.mjs +645 -0
- package/dist/guide/index.js +3 -3
- package/dist/guide/index.mjs +3 -3
- package/dist/index.d.mts +21 -252
- package/dist/index.d.ts +21 -252
- package/dist/index.js +22 -22
- package/dist/index.mjs +186 -791
- package/package.json +8 -1
- package/src/AchievementCatalogSection.tsx +1 -1
- package/src/AchievementRow.tsx +9 -9
- package/src/BaseSettingsDialog.tsx +36 -34
- package/src/BaseStatsScreen.tsx +13 -13
- package/src/ConfirmDialog.tsx +2 -2
- package/src/ControlSchemePicker.tsx +9 -9
- package/src/CornerActionButtons.tsx +6 -6
- package/src/CornerStatusBadges.tsx +8 -8
- package/src/InlineColorPicker.tsx +30 -30
- package/src/LabeledDropdown.tsx +8 -8
- package/src/PlayerSetupPanel.tsx +25 -25
- package/src/PopoverBody.tsx +16 -16
- package/src/PressAwayOverlay.tsx +2 -2
- package/src/ReadyButton.tsx +3 -3
- package/src/SectionedDropdown.tsx +31 -31
- package/src/SharedActionBand.tsx +6 -6
- package/src/SkiaGate.tsx +5 -5
- package/src/SkiaGate.web.tsx +2 -2
- package/src/StaggeredWord.tsx +9 -9
- package/src/StatRow.tsx +3 -3
- package/src/StatSection.tsx +3 -3
- package/src/TriggerGauge.tsx +54 -54
- package/src/TriggerGaugeHost.tsx +2 -2
- package/src/chrome/index.ts +22 -0
- package/src/fonts.ts +1 -1
- package/src/guide/DirectionKeysHint.tsx +3 -3
- package/src/guide/GuideProvider.tsx +7 -7
- package/src/guide/HowToPlayDialog.tsx +7 -7
- package/src/guide/SeatDiagram.tsx +3 -3
- package/src/guide/SwipeHint.tsx +4 -4
- package/src/guide/guideSlice.ts +7 -7
- package/src/guide/index.ts +4 -3
- package/src/guide/types.ts +1 -1
- package/src/index.ts +1 -1
- package/src/loadSkiaWeb.ts +2 -2
- package/src/loadSkiaWeb.web.ts +2 -2
- package/src/overlayCard.ts +3 -3
- package/src/useAutoAlign.ts +23 -23
- package/src/usePopoverHost.ts +1 -1
- package/src/useZoneClampedAlign.ts +23 -23
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/chrome/index.ts
|
|
31
|
+
var chrome_exports = {};
|
|
32
|
+
__export(chrome_exports, {
|
|
33
|
+
AchievementCatalogSection: () => AchievementCatalogSection,
|
|
34
|
+
AchievementRow: () => AchievementRow,
|
|
35
|
+
ActivityStatSection: () => ActivityStatSection,
|
|
36
|
+
BaseSettingsDialog: () => BaseSettingsDialog,
|
|
37
|
+
BaseStatsScreen: () => BaseStatsScreen,
|
|
38
|
+
ConfirmDialog: () => ConfirmDialog,
|
|
39
|
+
ContentGutter: () => ContentGutter,
|
|
40
|
+
CornerActionButtons: () => CornerActionButtons,
|
|
41
|
+
LOCKED_BADGE_COLOR: () => LOCKED_BADGE_COLOR,
|
|
42
|
+
MONO_FONT: () => MONO_FONT,
|
|
43
|
+
RotatedFrame: () => RotatedFrame,
|
|
44
|
+
StaggeredWord: () => StaggeredWord,
|
|
45
|
+
StatRow: () => StatRow,
|
|
46
|
+
StatSection: () => StatSection,
|
|
47
|
+
UpdateDialog: () => UpdateDialog,
|
|
48
|
+
getOverlayCardColors: () => getOverlayCardColors,
|
|
49
|
+
getStaggeredWordDuration: () => getStaggeredWordDuration,
|
|
50
|
+
overlayActionStyles: () => overlayActionStyles,
|
|
51
|
+
overlayStyles: () => overlayStyles
|
|
52
|
+
});
|
|
53
|
+
module.exports = __toCommonJS(chrome_exports);
|
|
54
|
+
|
|
55
|
+
// src/AchievementCatalogSection.tsx
|
|
56
|
+
var import_auto_paper2 = require("@rific/auto-paper");
|
|
57
|
+
var import_react = require("react");
|
|
58
|
+
var import_react_native3 = require("react-native");
|
|
59
|
+
var import_react_native_paper2 = require("react-native-paper");
|
|
60
|
+
|
|
61
|
+
// src/AchievementRow.tsx
|
|
62
|
+
var import_auto_paper = require("@rific/auto-paper");
|
|
63
|
+
var import_react_native = require("react-native");
|
|
64
|
+
var import_react_native_paper = require("react-native-paper");
|
|
65
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
66
|
+
var LOCKED_BADGE_COLOR = "rgba(128,128,128,0.3)";
|
|
67
|
+
function AchievementRow({ icon, title, description, badgeColor, unlockedLabel, checkColor, progress, deviceMarker, fg: fgOverride, fgMuted: fgMutedOverride, sectionBg: sectionBgOverride }) {
|
|
68
|
+
const { dark } = (0, import_auto_paper.useAutoPaperTheme)();
|
|
69
|
+
const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
|
|
70
|
+
const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
|
|
71
|
+
const sectionBg = sectionBgOverride ?? (dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)");
|
|
72
|
+
const unlocked = unlockedLabel !== void 0;
|
|
73
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: [styles.row, { backgroundColor: sectionBg }], children: [
|
|
74
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { style: [styles.badge, { backgroundColor: badgeColor }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source: icon, size: 20, color: unlocked ? "#000000" : fgMuted }) }),
|
|
75
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.text, children: [
|
|
76
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodyLarge", style: [styles.boldText, { color: fg }], children: title }),
|
|
77
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodySmall", style: { color: fgMuted }, children: description }),
|
|
78
|
+
deviceMarker && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.deviceMarker, children: [
|
|
79
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source: "earth", size: 11, color: fgMuted }),
|
|
80
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "labelSmall", style: { color: fgMuted }, children: "Same for every profile" })
|
|
81
|
+
] }),
|
|
82
|
+
!unlocked && progress !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.ProgressBar, { progress, color: checkColor ?? badgeColor, style: styles.progressBar })
|
|
83
|
+
] }),
|
|
84
|
+
unlocked ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.status, children: [
|
|
85
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source: "check-circle", size: 20, color: checkColor ?? badgeColor }),
|
|
86
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "labelSmall", style: { color: fgMuted }, children: unlockedLabel })
|
|
87
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source: "lock-outline", size: 20, color: fgMuted })
|
|
88
|
+
] });
|
|
89
|
+
}
|
|
90
|
+
var styles = import_react_native.StyleSheet.create({
|
|
91
|
+
badge: {
|
|
92
|
+
alignItems: "center",
|
|
93
|
+
borderRadius: 10,
|
|
94
|
+
height: 36,
|
|
95
|
+
justifyContent: "center",
|
|
96
|
+
width: 36
|
|
97
|
+
},
|
|
98
|
+
boldText: {
|
|
99
|
+
fontWeight: "bold"
|
|
100
|
+
},
|
|
101
|
+
deviceMarker: {
|
|
102
|
+
alignItems: "center",
|
|
103
|
+
flexDirection: "row",
|
|
104
|
+
gap: 4
|
|
105
|
+
},
|
|
106
|
+
progressBar: {
|
|
107
|
+
borderRadius: 4,
|
|
108
|
+
height: 6,
|
|
109
|
+
marginTop: 6
|
|
110
|
+
},
|
|
111
|
+
row: {
|
|
112
|
+
alignItems: "center",
|
|
113
|
+
borderRadius: 12,
|
|
114
|
+
flexDirection: "row",
|
|
115
|
+
gap: 12,
|
|
116
|
+
padding: 12
|
|
117
|
+
},
|
|
118
|
+
status: {
|
|
119
|
+
alignItems: "center",
|
|
120
|
+
gap: 2,
|
|
121
|
+
width: 64
|
|
122
|
+
},
|
|
123
|
+
text: {
|
|
124
|
+
flex: 1,
|
|
125
|
+
gap: 2
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// src/fonts.ts
|
|
130
|
+
var import_react_native2 = require("react-native");
|
|
131
|
+
var MONO_FONT = import_react_native2.Platform.select({ ios: "Menlo", android: "monospace", default: "monospace" });
|
|
132
|
+
|
|
133
|
+
// src/AchievementCatalogSection.tsx
|
|
134
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
135
|
+
function AchievementCatalogSection({ rows, label = "ALL ACHIEVEMENTS", fg, fgMuted: fgMutedOverride }) {
|
|
136
|
+
const { dark } = (0, import_auto_paper2.useAutoPaperTheme)();
|
|
137
|
+
const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
|
|
138
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react.Fragment, { children: [
|
|
139
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "labelMedium", style: [styles2.label, { color: fgMuted, fontFamily: MONO_FONT }], children: label }),
|
|
140
|
+
rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AchievementRow, { icon: row.icon, title: row.title, description: row.description, badgeColor: row.unlockedAt !== void 0 ? row.tierColor : LOCKED_BADGE_COLOR, checkColor: row.tierColor, unlockedLabel: row.unlockedLabel, progress: row.progress, deviceMarker: row.deviceMarker, fg, fgMuted: fgMutedOverride }, row.id))
|
|
141
|
+
] });
|
|
142
|
+
}
|
|
143
|
+
var styles2 = import_react_native3.StyleSheet.create({
|
|
144
|
+
label: {
|
|
145
|
+
letterSpacing: 2,
|
|
146
|
+
marginTop: 8
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// src/StatRow.tsx
|
|
151
|
+
var import_auto_paper3 = require("@rific/auto-paper");
|
|
152
|
+
var import_react_native4 = require("react-native");
|
|
153
|
+
var import_react_native_paper3 = require("react-native-paper");
|
|
154
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
155
|
+
function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }) {
|
|
156
|
+
const { dark } = (0, import_auto_paper3.useAutoPaperTheme)();
|
|
157
|
+
const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
|
|
158
|
+
const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
|
|
159
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.row, children: [
|
|
160
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyMedium", style: { color: fgMuted }, children: label }),
|
|
161
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyMedium", style: [styles3.value, { color: fg }], children: value })
|
|
162
|
+
] });
|
|
163
|
+
}
|
|
164
|
+
var styles3 = import_react_native4.StyleSheet.create({
|
|
165
|
+
row: {
|
|
166
|
+
alignItems: "center",
|
|
167
|
+
flexDirection: "row",
|
|
168
|
+
justifyContent: "space-between"
|
|
169
|
+
},
|
|
170
|
+
value: {
|
|
171
|
+
fontWeight: "bold"
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// src/StatSection.tsx
|
|
176
|
+
var import_auto_paper4 = require("@rific/auto-paper");
|
|
177
|
+
var import_react_native5 = require("react-native");
|
|
178
|
+
var import_react_native_paper4 = require("react-native-paper");
|
|
179
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
180
|
+
function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, children }) {
|
|
181
|
+
const { dark } = (0, import_auto_paper4.useAutoPaperTheme)();
|
|
182
|
+
const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
|
|
183
|
+
const sectionBg = sectionBgOverride ?? (dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)");
|
|
184
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native5.View, { style: [styles4.section, { backgroundColor: sectionBg }], children: [
|
|
185
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper4.Text, { variant: "labelMedium", style: [styles4.label, { color: fg }], children: label }),
|
|
186
|
+
children
|
|
187
|
+
] });
|
|
188
|
+
}
|
|
189
|
+
var styles4 = import_react_native5.StyleSheet.create({
|
|
190
|
+
// Bold and full-contrast (fg), not the original fontless/fgMuted treatment - that read at the
|
|
191
|
+
// same visual weight as StatRow's own row labels below it (both used the identical muted color;
|
|
192
|
+
// letterSpacing/variant alone didn't separate them enough to read as "this is the group header"
|
|
193
|
+
// at a glance). Matches AchievementRow's own title styling (bold + fg) below it in the same list.
|
|
194
|
+
label: {
|
|
195
|
+
fontWeight: "bold",
|
|
196
|
+
letterSpacing: 2
|
|
197
|
+
},
|
|
198
|
+
section: {
|
|
199
|
+
borderRadius: 12,
|
|
200
|
+
gap: 8,
|
|
201
|
+
padding: 16
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// src/ActivityStatSection.tsx
|
|
206
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
207
|
+
function ActivityStatSection({ stats }) {
|
|
208
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(StatSection, { label: "ACTIVITY", children: [
|
|
209
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Days Played", value: String(stats.distinctDaysPlayed) }),
|
|
210
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Day Streak", value: String(stats.currentDayStreak) }),
|
|
211
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Best Day Streak", value: String(stats.bestDayStreak) })
|
|
212
|
+
] });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/BaseSettingsDialog.tsx
|
|
216
|
+
var import_auto_paper6 = require("@rific/auto-paper");
|
|
217
|
+
var import_feedback_press2 = require("@rific/feedback-press");
|
|
218
|
+
var import_updater = require("@rific/updater");
|
|
219
|
+
var import_core3 = require("@tastic/core");
|
|
220
|
+
var import_react4 = require("react");
|
|
221
|
+
var import_react_native9 = require("react-native");
|
|
222
|
+
var import_react_native_paper7 = require("react-native-paper");
|
|
223
|
+
|
|
224
|
+
// src/ConfirmDialog.tsx
|
|
225
|
+
var import_auto_paper5 = require("@rific/auto-paper");
|
|
226
|
+
var import_feedback_press = require("@rific/feedback-press");
|
|
227
|
+
var import_core = require("@tastic/core");
|
|
228
|
+
var import_react_native7 = require("react-native");
|
|
229
|
+
var import_react_native_paper5 = require("react-native-paper");
|
|
230
|
+
|
|
231
|
+
// src/overlayCard.ts
|
|
232
|
+
var import_react_native6 = require("react-native");
|
|
233
|
+
function getOverlayCardColors(dark) {
|
|
234
|
+
return {
|
|
235
|
+
fg: dark ? "#FFFFFF" : "#000000",
|
|
236
|
+
fgMuted: dark ? "rgba(255,255,255,0.4)" : "rgba(0,0,0,0.4)",
|
|
237
|
+
cardBg: dark ? "#111111" : "#F2F2F2",
|
|
238
|
+
cardBorder: dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)"
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
var overlayStyles = import_react_native6.StyleSheet.create({
|
|
242
|
+
card: {
|
|
243
|
+
alignItems: "center",
|
|
244
|
+
borderRadius: 20,
|
|
245
|
+
borderWidth: 1,
|
|
246
|
+
gap: 16,
|
|
247
|
+
maxWidth: 360,
|
|
248
|
+
padding: 32
|
|
249
|
+
},
|
|
250
|
+
overlay: {
|
|
251
|
+
alignItems: "center",
|
|
252
|
+
backgroundColor: "rgba(0,0,0,0.72)",
|
|
253
|
+
bottom: 0,
|
|
254
|
+
justifyContent: "center",
|
|
255
|
+
left: 0,
|
|
256
|
+
position: "absolute",
|
|
257
|
+
right: 0,
|
|
258
|
+
top: 0
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
var overlayActionStyles = import_react_native6.StyleSheet.create({
|
|
262
|
+
// minWidth (not flexBasis) is each button's real floor - whenever both fit side by side, flexGrow
|
|
263
|
+
// still splits any leftover width evenly beyond that floor, same visual result as a plain flex: 1
|
|
264
|
+
// on a roomy screen, but a button can never be squeezed narrower than 128, only wrap to its own
|
|
265
|
+
// row instead (see actions' flexWrap below). This used to be flexBasis: 128, which measured
|
|
266
|
+
// correctly on web but rendered every button roughly SQUARE on iOS - confirmed live (see the
|
|
267
|
+
// "Quit Match?" Cancel/Quit buttons and the single-button "OK" dialogs both coming out ~128pt
|
|
268
|
+
// tall instead of wide). Root cause: react-native-paper's Button renders its Surface as two
|
|
269
|
+
// nested views on iOS only (Surface.tsx's SurfaceIOS, for shadow rendering) - only a curated
|
|
270
|
+
// allowlist of style keys (flex/flexGrow/flexShrink/width/height/position/... - notably NOT
|
|
271
|
+
// flexBasis) reaches the outer view, which is the real flex item inside this row; every other
|
|
272
|
+
// key, flexBasis included, falls through to an inner view whose own unspecified flexDirection
|
|
273
|
+
// defaults to column. flexGrow: 1 landed on the (row-context) outer view and grew width
|
|
274
|
+
// correctly; flexBasis: 128 landed on the (column-context) inner view instead, where a flexBasis
|
|
275
|
+
// sets HEIGHT, not width. minWidth isn't axis-relative like flexBasis, so it constrains width
|
|
276
|
+
// correctly regardless of which of the two views it ends up on. react-native-paper's Button also
|
|
277
|
+
// hardcodes numberOfLines={1} on its label with no override, so a button squeezed under its
|
|
278
|
+
// label's natural width silently ellipsizes ("Cancel" -> "Can...") instead of wrapping - assumed
|
|
279
|
+
// fixable by just giving the row more width, until confirmed live that these two-word labels
|
|
280
|
+
// (Cancel/Reset/New Game) can still lose the truncation fight even with the extra room ConfirmDialog's own
|
|
281
|
+
// card width fix provides, on a narrow enough screen.
|
|
282
|
+
actionButton: {
|
|
283
|
+
flexGrow: 1,
|
|
284
|
+
minWidth: 128
|
|
285
|
+
},
|
|
286
|
+
// A lone confirm button (no cancel - BaseSettingsDialog's info-only "OK" dialogs, e.g. the
|
|
287
|
+
// update-check "no update found" message) has nothing left to share actions' width with, so
|
|
288
|
+
// actionButton's own flexGrow: 1 would stretch it across the entire row instead of reading as a
|
|
289
|
+
// normal button. Same 128 floor, no flexGrow, so it stays at that natural size instead of filling
|
|
290
|
+
// the card - centered by actions' own justifyContent below.
|
|
291
|
+
actionButtonSingle: {
|
|
292
|
+
minWidth: 128
|
|
293
|
+
},
|
|
294
|
+
actions: {
|
|
295
|
+
flexDirection: "row",
|
|
296
|
+
flexWrap: "wrap",
|
|
297
|
+
gap: 12,
|
|
298
|
+
justifyContent: "center",
|
|
299
|
+
width: "100%"
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// src/ConfirmDialog.tsx
|
|
304
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
305
|
+
function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation = 0, icon = "help-circle-outline", destructive = false }) {
|
|
306
|
+
const { dark, colors } = (0, import_auto_paper5.useAutoPaperTheme)();
|
|
307
|
+
if (!visible) return null;
|
|
308
|
+
const { fg, cardBg, cardBorder } = getOverlayCardColors(dark);
|
|
309
|
+
const accent = destructive ? colors.danger : colors.primary;
|
|
310
|
+
const onAccent = destructive ? colors.onDanger : colors.onPrimary;
|
|
311
|
+
const showCancel = cancelLabel !== void 0 && onCancel !== void 0;
|
|
312
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper5.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native7.View, { style: [overlayStyles.overlay, styles5.overlay], children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native7.View, { style: [overlayStyles.card, styles5.card, { backgroundColor: cardBg, borderColor: cardBorder }, (0, import_core.toRotationStyle)(rotation)], children: [
|
|
313
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper5.Icon, { source: icon, size: 64, color: accent }),
|
|
314
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper5.Text, { variant: "headlineLarge", style: [styles5.title, { color: accent }], children: title }),
|
|
315
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper5.Text, { variant: "bodyLarge", style: [styles5.body, { color: fg }], children: message }),
|
|
316
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native7.View, { style: overlayActionStyles.actions, children: [
|
|
317
|
+
showCancel && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_feedback_press.Button, { mode: "outlined", onPress: onCancel, style: overlayActionStyles.actionButton, children: cancelLabel }),
|
|
318
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_feedback_press.Button, { mode: "contained", onPress: onConfirm, style: showCancel ? overlayActionStyles.actionButton : overlayActionStyles.actionButtonSingle, buttonColor: accent, textColor: onAccent, children: confirmLabel })
|
|
319
|
+
] })
|
|
320
|
+
] }) }) });
|
|
321
|
+
}
|
|
322
|
+
var styles5 = import_react_native7.StyleSheet.create({
|
|
323
|
+
body: {
|
|
324
|
+
textAlign: "center"
|
|
325
|
+
},
|
|
326
|
+
// Additive on top of overlayCard's own shared `card` shape (borderRadius/borderWidth/gap/maxWidth/
|
|
327
|
+
// padding) - claims its full allowance instead of just maxWidth alone, which only caps how wide the
|
|
328
|
+
// card is allowed to get - without an explicit width, a flex-centered child sizes to its own content
|
|
329
|
+
// instead of stretching, so the card (and therefore actions below) can end up narrower than the
|
|
330
|
+
// screen actually has room for, needlessly forcing the action row to wrap sooner than it has to.
|
|
331
|
+
card: {
|
|
332
|
+
width: "100%"
|
|
333
|
+
},
|
|
334
|
+
// Additive on top of overlayCard's own shared `overlay` backdrop - gives the card room to breathe
|
|
335
|
+
// from the screen edges now that it reliably stretches to fill its full width allowance (see
|
|
336
|
+
// card's own width comment) - without this, width: '100%' would run the card edge-to-edge on a
|
|
337
|
+
// screen narrower than maxWidth's own 360.
|
|
338
|
+
overlay: {
|
|
339
|
+
paddingHorizontal: 24
|
|
340
|
+
},
|
|
341
|
+
// textAlign is load-bearing, not decorative, once `title` wraps to more than one line - centered
|
|
342
|
+
// within card's own alignItems: 'center' only centers the Text's block as a whole; each wrapped
|
|
343
|
+
// line inside that block still defaults to left-aligned without this.
|
|
344
|
+
title: {
|
|
345
|
+
fontWeight: "bold",
|
|
346
|
+
textAlign: "center"
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// src/RotatedFrame.tsx
|
|
351
|
+
var import_core2 = require("@tastic/core");
|
|
352
|
+
var import_react2 = require("react");
|
|
353
|
+
var import_react_native8 = require("react-native");
|
|
354
|
+
var import_react_native_paper6 = require("react-native-paper");
|
|
355
|
+
var import_react_native_safe_area_context = require("react-native-safe-area-context");
|
|
356
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
357
|
+
function RotatedFrame({ children, rotation: rotationOverride, rotatedSafeArea = false, portalHost = false, passThrough = false, maxContentWidth }) {
|
|
358
|
+
const ambientRotation = (0, import_core2.useRotation)();
|
|
359
|
+
const rotation = rotationOverride ?? ambientRotation;
|
|
360
|
+
const window = (0, import_react_native8.useWindowDimensions)();
|
|
361
|
+
const { width, height } = (0, import_core2.rotateDimensions)(window.width, window.height, rotation);
|
|
362
|
+
const rawInsets = (0, import_react_native_safe_area_context.useSafeAreaInsets)();
|
|
363
|
+
const insets = (0, import_react2.useMemo)(() => rotatedSafeArea ? (0, import_core2.rotateInsets)(rawInsets, rotation) : rawInsets, [rotatedSafeArea, rawInsets, rotation]);
|
|
364
|
+
const cappedWidth = maxContentWidth !== void 0 && width > height ? maxContentWidth : void 0;
|
|
365
|
+
const column = /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native8.View, { style: [styles6.column, cappedWidth !== void 0 && { maxWidth: cappedWidth }], pointerEvents: passThrough ? "box-none" : void 0, children });
|
|
366
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_core2.FakeLandscapeView, { rotation, passThrough, style: styles6.root, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_safe_area_context.SafeAreaInsetsContext.Provider, { value: insets, children: portalHost ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper6.Portal.Host, { children: column }) : column }) });
|
|
367
|
+
}
|
|
368
|
+
var styles6 = import_react_native8.StyleSheet.create({
|
|
369
|
+
// A centred column: full width up to `maxWidth`, so a capped frame sits in the middle of a wider window.
|
|
370
|
+
column: {
|
|
371
|
+
alignSelf: "center",
|
|
372
|
+
flex: 1,
|
|
373
|
+
width: "100%"
|
|
374
|
+
},
|
|
375
|
+
root: {
|
|
376
|
+
flex: 1
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// src/useUpdateConfirmBridge.ts
|
|
381
|
+
var import_react3 = require("react");
|
|
382
|
+
function useUpdateConfirmBridge() {
|
|
383
|
+
const [manifest, setManifest] = (0, import_react3.useState)(null);
|
|
384
|
+
const resolveRef = (0, import_react3.useRef)(null);
|
|
385
|
+
const onConfirm = (0, import_react3.useCallback)(
|
|
386
|
+
(next) => new Promise((resolve) => {
|
|
387
|
+
resolveRef.current = resolve;
|
|
388
|
+
setManifest(next);
|
|
389
|
+
}),
|
|
390
|
+
[]
|
|
391
|
+
);
|
|
392
|
+
const respond = (0, import_react3.useCallback)((confirmed) => {
|
|
393
|
+
setManifest(null);
|
|
394
|
+
resolveRef.current?.(confirmed);
|
|
395
|
+
resolveRef.current = null;
|
|
396
|
+
}, []);
|
|
397
|
+
return { manifest, onConfirm, respond };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/BaseSettingsDialog.tsx
|
|
401
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
402
|
+
function SettingIcon({ source, color, containerColor }) {
|
|
403
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native9.View, { style: [styles7.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Icon, { source, size: 18, color }) });
|
|
404
|
+
}
|
|
405
|
+
function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, onShowHowToPlay, children }) {
|
|
406
|
+
const { colors } = (0, import_auto_paper6.useAutoPaperTheme)();
|
|
407
|
+
const ambientRotation = (0, import_core3.useRotation)();
|
|
408
|
+
const rotation = rotationOverride ?? ambientRotation;
|
|
409
|
+
const isTouchPrimary = (0, import_core3.useIsTouchPrimaryDevice)();
|
|
410
|
+
const { settings: hapticSettings, set: setHapticSettings } = (0, import_feedback_press2.useHapticSettings)();
|
|
411
|
+
const { settings: soundSettings, set: setSoundSettings } = (0, import_feedback_press2.useSoundSettings)();
|
|
412
|
+
const sound = (0, import_react4.useContext)(import_feedback_press2.SoundContext);
|
|
413
|
+
const { forceShort: forceHapticFeedback } = (0, import_feedback_press2.useVibration)();
|
|
414
|
+
const [infoMessage, setInfoMessage] = (0, import_react4.useState)(null);
|
|
415
|
+
const { manifest: updateManifest, onConfirm: onUpdateConfirm, respond: respondToUpdateConfirm } = useUpdateConfirmBridge();
|
|
416
|
+
const { check, checking, updateReady } = (0, import_updater.useUpdater)({
|
|
417
|
+
autoCheck: false,
|
|
418
|
+
autoPrompt: false,
|
|
419
|
+
onConfirm: onUpdateConfirm,
|
|
420
|
+
onError: onUpdateError,
|
|
421
|
+
onInfo: (title, message) => setInfoMessage({ title, message })
|
|
422
|
+
});
|
|
423
|
+
const updateManifestDate = updateManifest ? new Date(updateManifest.createdAt) : null;
|
|
424
|
+
const [opened, setOpened] = (0, import_react4.useState)(visible);
|
|
425
|
+
if (visible && !opened) setOpened(true);
|
|
426
|
+
const showLockOrientation = isTouchPrimary && lockOrientation !== void 0 && onLockOrientationChange !== void 0;
|
|
427
|
+
const showEdgeGuard = import_react_native9.Platform.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
|
|
428
|
+
const showHaptics = import_react_native9.Platform.OS !== "web" && !hideHaptics;
|
|
429
|
+
const showSound = !hideSound;
|
|
430
|
+
const showAppearance = !hideAppearance;
|
|
431
|
+
const showUpdateCheck = import_react_native9.Platform.OS !== "web" && !hideUpdateCheck;
|
|
432
|
+
if (!opened) return null;
|
|
433
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(RotatedFrame, { rotation, rotatedSafeArea: true, portalHost: true, passThrough: true, children: [
|
|
434
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_auto_paper6.Dialog, { visible, onDismiss, style: styles7.dialog, children: [
|
|
435
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_auto_paper6.Dialog.Title, { children: "Settings" }),
|
|
436
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_auto_paper6.Dialog.ScrollArea, { style: styles7.scrollArea, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.ScrollView, { contentContainerStyle: styles7.content, showsVerticalScrollIndicator: false, children: [
|
|
437
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.toggleGroup, children: [
|
|
438
|
+
showLockOrientation && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => onLockOrientationChange(!lockOrientation), style: styles7.toggleButton, accessibilityLabel: `Lock orientation ${lockOrientation ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.toggleContent, children: [
|
|
439
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SettingIcon, { source: lockOrientation ? "lock" : "lock-open-variant-outline", color: lockOrientation ? colors.tertiary : colors.onSurfaceVariant, containerColor: lockOrientation ? colors.tertiaryContainer : colors.surfaceVariant }),
|
|
440
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.flexShrink, children: [
|
|
441
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
|
|
442
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Pins the current layout" })
|
|
443
|
+
] })
|
|
444
|
+
] }) }),
|
|
445
|
+
showEdgeGuard && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => onDeferBottomEdgeGestures(!deferBottomEdgeGestures), style: styles7.toggleButton, accessibilityLabel: `Edge guard ${deferBottomEdgeGestures ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.toggleContent, children: [
|
|
446
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SettingIcon, { source: deferBottomEdgeGestures ? "shield-check-outline" : "shield-off-outline", color: deferBottomEdgeGestures ? colors.tertiary : colors.onSurfaceVariant, containerColor: deferBottomEdgeGestures ? colors.tertiaryContainer : colors.surfaceVariant }),
|
|
447
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.flexShrink, children: [
|
|
448
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
|
|
449
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Requires a second swipe" })
|
|
450
|
+
] })
|
|
451
|
+
] }) }),
|
|
452
|
+
(showSound || showHaptics) && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.toggleRow, children: [
|
|
453
|
+
showSound && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
454
|
+
import_feedback_press2.TouchableRipple,
|
|
455
|
+
{
|
|
456
|
+
soundDisabled: true,
|
|
457
|
+
onPress: () => {
|
|
458
|
+
const enabled = !soundSettings.enabled;
|
|
459
|
+
setSoundSettings({ enabled });
|
|
460
|
+
if (enabled) sound.selection?.();
|
|
461
|
+
},
|
|
462
|
+
style: styles7.toggleButtonInRow,
|
|
463
|
+
accessibilityLabel: `Sound ${soundSettings.enabled ? "on" : "off"}`,
|
|
464
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.toggleContent, children: [
|
|
465
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SettingIcon, { source: soundSettings.enabled ? "volume-high" : "volume-off", color: soundSettings.enabled ? colors.tertiary : colors.onSurfaceVariant, containerColor: soundSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant }),
|
|
466
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
|
|
467
|
+
] })
|
|
468
|
+
}
|
|
469
|
+
),
|
|
470
|
+
showHaptics && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
471
|
+
import_feedback_press2.TouchableRipple,
|
|
472
|
+
{
|
|
473
|
+
hapticDisabled: true,
|
|
474
|
+
onPress: () => {
|
|
475
|
+
const vibrate = !hapticSettings.vibrate;
|
|
476
|
+
setHapticSettings({ vibrate });
|
|
477
|
+
if (vibrate) forceHapticFeedback();
|
|
478
|
+
},
|
|
479
|
+
style: styles7.toggleButtonInRow,
|
|
480
|
+
accessibilityLabel: `Haptics ${hapticSettings.vibrate ? "on" : "off"}`,
|
|
481
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.toggleContent, children: [
|
|
482
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SettingIcon, { source: hapticSettings.vibrate ? "vibrate" : "vibrate-off", color: hapticSettings.vibrate ? colors.tertiary : colors.onSurfaceVariant, containerColor: hapticSettings.vibrate ? colors.tertiaryContainer : colors.surfaceVariant }),
|
|
483
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
|
|
484
|
+
] })
|
|
485
|
+
}
|
|
486
|
+
)
|
|
487
|
+
] }),
|
|
488
|
+
onShowHowToPlay && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
489
|
+
import_feedback_press2.TouchableRipple,
|
|
490
|
+
{
|
|
491
|
+
onPress: () => {
|
|
492
|
+
onDismiss();
|
|
493
|
+
onShowHowToPlay();
|
|
494
|
+
},
|
|
495
|
+
style: styles7.toggleButton,
|
|
496
|
+
accessibilityLabel: "How to Play",
|
|
497
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.toggleContent, children: [
|
|
498
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SettingIcon, { source: "help-circle-outline", color: colors.secondary, containerColor: colors.secondaryContainer }),
|
|
499
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.flexShrink, children: [
|
|
500
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "How to Play" }),
|
|
501
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Replay the quick intro" })
|
|
502
|
+
] })
|
|
503
|
+
] })
|
|
504
|
+
}
|
|
505
|
+
)
|
|
506
|
+
] }),
|
|
507
|
+
showAppearance && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.section, children: [
|
|
508
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "labelMedium", style: [styles7.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
|
|
509
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_auto_paper6.AutoAppearancePicker, { showLabels: false })
|
|
510
|
+
] }),
|
|
511
|
+
children,
|
|
512
|
+
showUpdateCheck && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.section, children: [
|
|
513
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native_paper7.Text, { variant: "labelSmall", style: [styles7.sectionLabel, { color: colors.onSurfaceVariant }], children: [
|
|
514
|
+
"VERSION ",
|
|
515
|
+
version,
|
|
516
|
+
updateReady ? " \xB7 UPDATE READY" : ""
|
|
517
|
+
] }),
|
|
518
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press2.Button, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
|
|
519
|
+
] })
|
|
520
|
+
] }) })
|
|
521
|
+
] }),
|
|
522
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ConfirmDialog, { visible: !!updateManifest, title: "Update Available", message: updateManifestDate ? `Released ${updateManifestDate.toLocaleDateString()} at ${updateManifestDate.toLocaleTimeString()}` : "", confirmLabel: "Restart", cancelLabel: "Later", icon: "update", rotation: 0, onConfirm: () => respondToUpdateConfirm(true), onCancel: () => respondToUpdateConfirm(false) }),
|
|
523
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ConfirmDialog, { visible: !!infoMessage, title: infoMessage?.title ?? "", message: infoMessage?.message ?? "", confirmLabel: "OK", icon: "information-outline", rotation: 0, onConfirm: () => setInfoMessage(null) })
|
|
524
|
+
] }) });
|
|
525
|
+
}
|
|
526
|
+
var styles7 = import_react_native9.StyleSheet.create({
|
|
527
|
+
content: {
|
|
528
|
+
gap: 24,
|
|
529
|
+
paddingVertical: 20
|
|
530
|
+
},
|
|
531
|
+
// Caps the card so it never grows past the screen - without this the dialog just keeps
|
|
532
|
+
// growing to fit its content and the overflow gets clipped by the screen edge, which is
|
|
533
|
+
// what happened in landscape where there's less height to work with. Dialog.ScrollArea +
|
|
534
|
+
// ScrollView below then take over and let the content scroll within that bound.
|
|
535
|
+
dialog: {
|
|
536
|
+
maxHeight: "90%"
|
|
537
|
+
},
|
|
538
|
+
flexShrink: {
|
|
539
|
+
flexShrink: 1
|
|
540
|
+
},
|
|
541
|
+
iconBadge: {
|
|
542
|
+
alignItems: "center",
|
|
543
|
+
borderRadius: 10,
|
|
544
|
+
height: 36,
|
|
545
|
+
justifyContent: "center",
|
|
546
|
+
width: 36
|
|
547
|
+
},
|
|
548
|
+
scrollArea: {
|
|
549
|
+
marginBottom: 0
|
|
550
|
+
},
|
|
551
|
+
section: {
|
|
552
|
+
gap: 12
|
|
553
|
+
},
|
|
554
|
+
sectionLabel: {
|
|
555
|
+
letterSpacing: 2
|
|
556
|
+
},
|
|
557
|
+
// Used to carry marginHorizontal: -12 + paddingHorizontal: 12 (netting to the same inset as
|
|
558
|
+
// APPEARANCE/SOUND & HAPTICS below, while giving the ripple/hover highlight room to breathe
|
|
559
|
+
// into the dialog's own gutter beyond that). Removed: this dialog scrolls (Dialog.ScrollArea +
|
|
560
|
+
// ScrollView above), and on web a ScrollView's own content wrapper clips to its own bounds
|
|
561
|
+
// regardless of a child's negative margin - confirmed live, the icon's own rounded-square badge
|
|
562
|
+
// was getting its left edge sliced off by exactly that overhang. No horizontal inset of its own
|
|
563
|
+
// now - it relies purely on the same dialog padding APPEARANCE/every other row already does,
|
|
564
|
+
// which can never overflow because it's never negative.
|
|
565
|
+
toggleButton: {
|
|
566
|
+
borderRadius: 12,
|
|
567
|
+
overflow: "hidden",
|
|
568
|
+
paddingVertical: 10
|
|
569
|
+
},
|
|
570
|
+
// Same shape as toggleButton, `flex: 1` instead of its own width - two of these side by side in
|
|
571
|
+
// toggleRow share the row's own edge alignment instead of each carrying their own.
|
|
572
|
+
toggleButtonInRow: {
|
|
573
|
+
borderRadius: 12,
|
|
574
|
+
flex: 1,
|
|
575
|
+
overflow: "hidden",
|
|
576
|
+
paddingVertical: 10
|
|
577
|
+
},
|
|
578
|
+
toggleContent: {
|
|
579
|
+
alignItems: "center",
|
|
580
|
+
flexDirection: "row",
|
|
581
|
+
gap: 12
|
|
582
|
+
},
|
|
583
|
+
toggleGroup: {
|
|
584
|
+
gap: 4
|
|
585
|
+
},
|
|
586
|
+
// No horizontal margin of its own now either - see toggleButton's identical note.
|
|
587
|
+
toggleRow: {
|
|
588
|
+
flexDirection: "row",
|
|
589
|
+
gap: 4
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// src/BaseStatsScreen.tsx
|
|
594
|
+
var import_auto_paper7 = require("@rific/auto-paper");
|
|
595
|
+
var import_feedback_press3 = require("@rific/feedback-press");
|
|
596
|
+
var import_core4 = require("@tastic/core");
|
|
597
|
+
var import_react5 = require("react");
|
|
598
|
+
var import_react_native10 = require("react-native");
|
|
599
|
+
var import_react_native_paper8 = require("react-native-paper");
|
|
600
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
601
|
+
function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", titleVariant = "headlineSmall", onBack, insets, onReset, resetLabel = "Reset All Stats", resetConfirmTitle = "Reset Everything?", resetConfirmBody = "This permanently erases all stats and achievements. This cannot be undone.", fg: fgOverride, bg: bgOverride, children }) {
|
|
602
|
+
const { dark, colors } = (0, import_auto_paper7.useAutoPaperTheme)();
|
|
603
|
+
const ambientRotation = (0, import_core4.useRotation)();
|
|
604
|
+
const rotation = rotationOverride ?? ambientRotation;
|
|
605
|
+
const [confirmResetVisible, setConfirmResetVisible] = (0, import_react5.useState)(false);
|
|
606
|
+
const showReset = !!onReset;
|
|
607
|
+
const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
|
|
608
|
+
const bg = bgOverride ?? (dark ? "#000000" : "#FFFFFF");
|
|
609
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native10.View, { style: [styles8.container, { backgroundColor: bg }], children: [
|
|
610
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native10.View, { style: [styles8.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
|
|
611
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_feedback_press3.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: onBack }),
|
|
612
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper8.Text, { variant: titleVariant, style: [styles8.title, { color: fg }], children: title })
|
|
613
|
+
] }),
|
|
614
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native10.ScrollView, { style: styles8.scrollView, contentContainerStyle: [styles8.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
|
|
615
|
+
children,
|
|
616
|
+
showReset && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_feedback_press3.Button, { mode: "contained", onPress: () => setConfirmResetVisible(true), buttonColor: colors.danger, textColor: colors.onDanger, style: styles8.resetButton, children: resetLabel })
|
|
617
|
+
] }),
|
|
618
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
619
|
+
ConfirmDialog,
|
|
620
|
+
{
|
|
621
|
+
visible: confirmResetVisible,
|
|
622
|
+
title: resetConfirmTitle,
|
|
623
|
+
message: resetConfirmBody,
|
|
624
|
+
confirmLabel: "Reset",
|
|
625
|
+
cancelLabel: "Cancel",
|
|
626
|
+
icon: "alert-outline",
|
|
627
|
+
destructive: true,
|
|
628
|
+
rotation,
|
|
629
|
+
onCancel: () => setConfirmResetVisible(false),
|
|
630
|
+
onConfirm: () => {
|
|
631
|
+
onReset?.();
|
|
632
|
+
setConfirmResetVisible(false);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
)
|
|
636
|
+
] });
|
|
637
|
+
}
|
|
638
|
+
var styles8 = import_react_native10.StyleSheet.create({
|
|
639
|
+
container: {
|
|
640
|
+
flex: 1
|
|
641
|
+
},
|
|
642
|
+
content: {
|
|
643
|
+
gap: 12,
|
|
644
|
+
paddingHorizontal: 20
|
|
645
|
+
},
|
|
646
|
+
header: {
|
|
647
|
+
alignItems: "center",
|
|
648
|
+
flexDirection: "row",
|
|
649
|
+
gap: 4,
|
|
650
|
+
paddingBottom: 8
|
|
651
|
+
},
|
|
652
|
+
resetButton: {
|
|
653
|
+
marginTop: 16
|
|
654
|
+
},
|
|
655
|
+
// Bounds the ScrollView to the space `container`'s flex:1 actually gives it - without this, a
|
|
656
|
+
// ScrollView with only a contentContainerStyle isn't reliably height-constrained on native (it
|
|
657
|
+
// can render at its full unclipped content height instead of the screen's), which leaves the
|
|
658
|
+
// header's back button touch target unreliable underneath it.
|
|
659
|
+
scrollView: {
|
|
660
|
+
flex: 1
|
|
661
|
+
},
|
|
662
|
+
title: {
|
|
663
|
+
flexShrink: 1,
|
|
664
|
+
fontWeight: "bold"
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
// src/ContentGutter.tsx
|
|
669
|
+
var import_core5 = require("@tastic/core");
|
|
670
|
+
var import_react_native11 = require("react-native");
|
|
671
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
672
|
+
function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, children }) {
|
|
673
|
+
const { width: windowWidth } = (0, import_core5.useRotatedWindowDimensions)();
|
|
674
|
+
const { contentWidth, gutterWidth } = (0, import_core5.computeContentBounds)(windowWidth, maxContentWidth ?? Infinity);
|
|
675
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { style: [styles9.row, style], children: [
|
|
676
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native11.View, { style: [styles9.gutter, { width: gutterWidth }], children: leftGutter }),
|
|
677
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native11.View, { style: { width: contentWidth }, children }),
|
|
678
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native11.View, { style: [styles9.gutter, { width: gutterWidth }], children: rightGutter })
|
|
679
|
+
] });
|
|
680
|
+
}
|
|
681
|
+
var styles9 = import_react_native11.StyleSheet.create({
|
|
682
|
+
// overflow:'hidden' keeps an oversized leftGutter/rightGutter from bleeding into the content
|
|
683
|
+
// column instead of clipping cleanly at its own real width.
|
|
684
|
+
gutter: {
|
|
685
|
+
overflow: "hidden"
|
|
686
|
+
},
|
|
687
|
+
row: {
|
|
688
|
+
flexDirection: "row"
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
// src/CornerActionButtons.tsx
|
|
693
|
+
var import_feedback_press4 = require("@rific/feedback-press");
|
|
694
|
+
var import_react_native12 = require("react-native");
|
|
695
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
696
|
+
function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon = "arrow-left", backLabel = "Back" }) {
|
|
697
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
698
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_feedback_press4.IconButton, { icon: backIcon, iconColor: fg, size: 24, style: [styles10.back, { top: 8 + insets.top, left: 8 + insets.left }], onPress: onBack, accessibilityLabel: backLabel }),
|
|
699
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_feedback_press4.IconButton, { icon: "cog", iconColor: fg, size: 24, style: [styles10.topRight, { top: 8 + insets.top, right: 8 + insets.right }], onPress: onSettings, accessibilityLabel: "Settings" })
|
|
700
|
+
] });
|
|
701
|
+
}
|
|
702
|
+
var styles10 = import_react_native12.StyleSheet.create({
|
|
703
|
+
back: {
|
|
704
|
+
position: "absolute"
|
|
705
|
+
},
|
|
706
|
+
topRight: {
|
|
707
|
+
position: "absolute"
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
// src/StaggeredWord.tsx
|
|
712
|
+
var import_react6 = require("react");
|
|
713
|
+
var import_react_native13 = require("react-native");
|
|
714
|
+
var import_react_native_reanimated = __toESM(require("react-native-reanimated"));
|
|
715
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
716
|
+
var STAGGER_MS = 45;
|
|
717
|
+
var LETTER_DURATION_MS = 320;
|
|
718
|
+
function getStaggeredWordDuration(letterCount) {
|
|
719
|
+
return (letterCount - 1) * STAGGER_MS + LETTER_DURATION_MS;
|
|
720
|
+
}
|
|
721
|
+
function AnimatedLetter({ char, index, color, reducedMotion, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }) {
|
|
722
|
+
const progress = (0, import_react_native_reanimated.useSharedValue)(reducedMotion ? 1 : 0);
|
|
723
|
+
(0, import_react6.useEffect)(() => {
|
|
724
|
+
if (reducedMotion) return;
|
|
725
|
+
progress.value = (0, import_react_native_reanimated.withDelay)(index * STAGGER_MS, (0, import_react_native_reanimated.withTiming)(1, { duration: LETTER_DURATION_MS, easing: import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.back(1.5)) }));
|
|
726
|
+
}, [reducedMotion]);
|
|
727
|
+
const style = (0, import_react_native_reanimated.useAnimatedStyle)(() => ({
|
|
728
|
+
opacity: progress.value,
|
|
729
|
+
transform: [{ translateY: (1 - progress.value) * 12 }, { scale: 0.85 + progress.value * 0.15 }]
|
|
730
|
+
}));
|
|
731
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native_reanimated.default.Text, { allowFontScaling: false, style: [{ color, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }, style], children: char });
|
|
732
|
+
}
|
|
733
|
+
function StaggeredWord({ word, color, fontFamily, fontSize, lineHeight, reducedMotion, startIndex = 0, fontWeight = "bold", letterSpacing, style, onLayout }) {
|
|
734
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native13.View, { style: [styles11.row, style], onLayout, children: word.split("").map((char, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AnimatedLetter, { char, index: startIndex + i, color, reducedMotion, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }, i)) });
|
|
735
|
+
}
|
|
736
|
+
var styles11 = import_react_native13.StyleSheet.create({
|
|
737
|
+
row: {
|
|
738
|
+
flexDirection: "row"
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
// src/UpdateDialog.tsx
|
|
743
|
+
var import_updater2 = require("@rific/updater");
|
|
744
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
745
|
+
function UpdateDialog({ rotation = 0, autoCheck = true, autoPrompt = true, onError }) {
|
|
746
|
+
const { manifest, onConfirm, respond } = useUpdateConfirmBridge();
|
|
747
|
+
(0, import_updater2.useUpdater)({ autoCheck, autoPrompt, onConfirm, onError });
|
|
748
|
+
const date = manifest ? new Date(manifest.createdAt) : null;
|
|
749
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ConfirmDialog, { visible: !!manifest, title: "Update Available", message: date ? `Released ${date.toLocaleDateString()} at ${date.toLocaleTimeString()}` : "", confirmLabel: "Restart", cancelLabel: "Later", icon: "update", rotation, onConfirm: () => respond(true), onCancel: () => respond(false) });
|
|
750
|
+
}
|
|
751
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
752
|
+
0 && (module.exports = {
|
|
753
|
+
AchievementCatalogSection,
|
|
754
|
+
AchievementRow,
|
|
755
|
+
ActivityStatSection,
|
|
756
|
+
BaseSettingsDialog,
|
|
757
|
+
BaseStatsScreen,
|
|
758
|
+
ConfirmDialog,
|
|
759
|
+
ContentGutter,
|
|
760
|
+
CornerActionButtons,
|
|
761
|
+
LOCKED_BADGE_COLOR,
|
|
762
|
+
MONO_FONT,
|
|
763
|
+
RotatedFrame,
|
|
764
|
+
StaggeredWord,
|
|
765
|
+
StatRow,
|
|
766
|
+
StatSection,
|
|
767
|
+
UpdateDialog,
|
|
768
|
+
getOverlayCardColors,
|
|
769
|
+
getStaggeredWordDuration,
|
|
770
|
+
overlayActionStyles,
|
|
771
|
+
overlayStyles
|
|
772
|
+
});
|