Skip to content

Commit fc9584f

Browse files
committed
增加触觉反馈
1 parent 83845ac commit fc9584f

1 file changed

Lines changed: 49 additions & 15 deletions

File tree

Vision-OCR/App.tsx

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,26 @@ function clamp(v: number, a = 0, b = 1) {
6363
return Math.max(a, Math.min(b, v))
6464
}
6565

66+
type HapticLevel = 'light' | 'medium' | 'heavy'
67+
68+
function fireHaptic(level: HapticLevel) {
69+
try {
70+
const h = (globalThis as any).HapticFeedback
71+
if (!h) return
72+
if (level === 'light') {
73+
if (typeof h.lightImpact === 'function') h.lightImpact()
74+
else if (typeof h.mediumImpact === 'function') h.mediumImpact()
75+
return
76+
}
77+
if (level === 'heavy') {
78+
if (typeof h.heavyImpact === 'function') h.heavyImpact()
79+
else if (typeof h.mediumImpact === 'function') h.mediumImpact()
80+
return
81+
}
82+
if (typeof h.mediumImpact === 'function') h.mediumImpact()
83+
} catch {}
84+
}
85+
6686
function RectOverlay({
6787
box,
6888
selected,
@@ -75,22 +95,25 @@ function RectOverlay({
7595
const stroke = selected ? 'rgba(0,122,255,1)' : 'rgba(0,200,0,1)'
7696
const fill = selected ? 'rgba(0,122,255,0.18)' : 'rgba(0,200,0,0.06)'
7797
const hitPad = 6
98+
const handleTap = () => {
99+
fireHaptic('light')
100+
onTap()
101+
}
78102
return (
79103
<>
80104
<Rectangle
81105
// larger invisible hit area for easier tapping
82106
fill={'rgba(0,0,0,0.001)'}
83107
frame={{ width: box.width + hitPad * 2, height: box.height + hitPad * 2 }}
84108
position={{ x: box.left + box.width / 2, y: box.top + box.height / 2 }}
85-
onTapGesture={() => onTap()}
109+
onTapGesture={handleTap}
86110
/>
87111
<Rectangle
88112
// a faint green fill so it's visible and hit-testable
89113
fill={fill}
90114
stroke={stroke}
91115
frame={{ width: box.width, height: box.height }}
92116
position={{ x: box.left + box.width / 2, y: box.top + box.height / 2 }}
93-
onTapGesture={() => onTap()}
94117
/>
95118
</>
96119
)
@@ -105,6 +128,7 @@ function ToolbarButton(props: {
105128
font?: number | Font | { name: string; size: number }
106129
imageScale?: 'small' | 'medium' | 'large'
107130
background?: boolean
131+
haptic?: HapticLevel
108132
}) {
109133
const isActive = props.active ?? false
110134
const foreground = isActive ? 'systemBlue' : undefined
@@ -115,9 +139,13 @@ function ToolbarButton(props: {
115139
const padding = layout === 'vertical'
116140
? { top: 10, bottom: 10, left: 12, right: 12 }
117141
: { top: 6, bottom: 6, left: 10, right: 10 }
142+
const handlePress = () => {
143+
fireHaptic(props.haptic ?? 'medium')
144+
props.onPress()
145+
}
118146
return (
119147
<Button
120-
action={props.onPress}
148+
action={handlePress}
121149
buttonStyle="plain"
122150
background={hasBackground ? { style: 'secondarySystemBackground', shape: { type: 'rect', cornerRadius: 10 } } : undefined}
123151
>
@@ -146,6 +174,10 @@ function ToolbarButton(props: {
146174

147175
export default function App({ initialImage }: { initialImage?: UIImage | null }) {
148176
const dismiss = Navigation.useDismiss()
177+
const withHaptic = (action: () => void | Promise<void>, level: HapticLevel = 'medium') => () => {
178+
fireHaptic(level)
179+
void action()
180+
}
149181

150182
const [image, setImage] = useState<UIImage | null>(null)
151183
const [items, setItems] = useState<RecognizedItem[]>([])
@@ -486,7 +518,7 @@ export default function App({ initialImage }: { initialImage?: UIImage | null })
486518
<ZStack>
487519
<VStack spacing={8} padding toast={{ message: toastMsg, isPresented: toastShown, onChanged: (v: boolean) => setToastShown(v), duration: 2, position: 'bottom' }}>
488520
<HStack spacing={8} alignment="center" zIndex={2}>
489-
<Button title="" systemImage="xmark" buttonStyle="plain" action={() => dismiss()} />
521+
<Button title="" systemImage="xmark" buttonStyle="plain" action={withHaptic(() => dismiss(), 'light')} />
490522
<Text font="headline">Vision OCR</Text>
491523
<Spacer />
492524
{loading ? <ProgressView value={0.5} /> : null}
@@ -496,14 +528,14 @@ export default function App({ initialImage }: { initialImage?: UIImage | null })
496528
<VStack frame={{ maxWidth: 'infinity', maxHeight: 'infinity' }} alignment="center">
497529
<Spacer />
498530
<VStack spacing={10} alignment="center">
499-
<ToolbarButton title="相册" systemImage="photo" onPress={pickPhoto} layout="vertical" font={18} imageScale="large" background={false} />
500-
<ToolbarButton title="文件" systemImage="doc" onPress={pickFile} layout="vertical" font={18} imageScale="large" background={false} />
501-
<ToolbarButton title="拍照" systemImage="camera" onPress={takePhoto} layout="vertical" font={18} imageScale="large" background={false} />
502-
<ToolbarButton title="粘贴" systemImage="doc.on.clipboard" onPress={pasteImage} layout="vertical" font={18} imageScale="large" background={false} />
531+
<ToolbarButton title="相册" systemImage="photo" onPress={pickPhoto} layout="vertical" font={18} imageScale="large" background={false} haptic="medium" />
532+
<ToolbarButton title="文件" systemImage="doc" onPress={pickFile} layout="vertical" font={18} imageScale="large" background={false} haptic="medium" />
533+
<ToolbarButton title="拍照" systemImage="camera" onPress={takePhoto} layout="vertical" font={18} imageScale="large" background={false} haptic="medium" />
534+
<ToolbarButton title="粘贴" systemImage="doc.on.clipboard" onPress={pasteImage} layout="vertical" font={18} imageScale="large" background={false} haptic="medium" />
503535
<Toggle
504536
title="自动读取剪贴板"
505537
value={autoPaste}
506-
onChanged={(v: boolean) => setAutoPaste(v)}
538+
onChanged={(v: boolean) => { fireHaptic('light'); setAutoPaste(v) }}
507539
toggleStyle="switch"
508540
/>
509541
</VStack>
@@ -514,19 +546,21 @@ export default function App({ initialImage }: { initialImage?: UIImage | null })
514546
{image ? (
515547
<GroupBox label={<Label title="操作" systemImage="slider.horizontal.3" />} zIndex={1}>
516548
<HStack spacing={8}>
517-
<ToolbarButton title="重置图片" systemImage="xmark.bin" onPress={resetImage} />
549+
<ToolbarButton title="重置图片" systemImage="xmark.bin" onPress={resetImage} haptic="heavy" />
518550
<ToolbarButton
519551
title={multiSelect ? '合并复制' : '复制全部'}
520552
systemImage="doc.on.doc"
521553
onPress={multiSelect ? copySelectedMulti : copyAll}
554+
haptic="medium"
522555
/>
523556
<ToolbarButton
524557
title="多选"
525558
systemImage={multiSelect ? 'checkmark.circle.fill' : 'checkmark.circle'}
526559
onPress={toggleMultiSelect}
527560
active={multiSelect}
561+
haptic="light"
528562
/>
529-
{selectedItem ? <ToolbarButton title="取消选择" systemImage="xmark.circle" onPress={() => setSelectedId(null)} /> : null}
563+
{selectedItem ? <ToolbarButton title="取消选择" systemImage="xmark.circle" onPress={() => setSelectedId(null)} haptic="light" /> : null}
530564
</HStack>
531565
</GroupBox>
532566
) : null}
@@ -715,11 +749,11 @@ export default function App({ initialImage }: { initialImage?: UIImage | null })
715749
{/* <Text font="footnote">识别结果:{selectedItem.content}</Text> */}
716750
<TextField title="编辑" value={editorValue} onChanged={setEditorValue} prompt="编辑文本" />
717751
<HStack spacing={8}>
718-
<Button title="应用" action={() => { applyEdit(); showToast('更改已保存') }} />
719-
<Button title="重置" action={() => { resetItem(selectedItem.id); setEditorValue(selectedItem.content) }} />
720-
<Button title="复制" action={copySelected} />
752+
<Button title="应用" action={withHaptic(() => { applyEdit(); showToast('更改已保存') }, 'medium')} />
753+
<Button title="重置" action={withHaptic(() => { resetItem(selectedItem.id); setEditorValue(selectedItem.content) }, 'light')} />
754+
<Button title="复制" action={withHaptic(copySelected, 'medium')} />
721755
<Spacer />
722-
<Button title="关闭" action={() => setSelectedId(null)} />
756+
<Button title="关闭" action={withHaptic(() => setSelectedId(null), 'light')} />
723757
</HStack>
724758
</VStack>
725759
</GroupBox>

0 commit comments

Comments
 (0)