inset_services/haptic_feedback.rs
1//! Flutter `services/haptic_feedback.dart`.
2
3use inset_embedder::HapticFeedbackType;
4use inset_foundation::App;
5
6/// Allows access to the haptic feedback interface on the device.
7///
8/// This API is intentionally terse since it calls default platform behavior. It
9/// is not suitable for precise control of the system's haptic feedback module.
10///
11/// See also:
12///
13/// * [Human Interface Haptics Guidelines](https://developer.apple.com/design/human-interface-guidelines/playing-haptics)
14pub struct HapticFeedback;
15
16impl HapticFeedback {
17 /// Provides vibration haptic feedback to the user for a short duration.
18 ///
19 /// On iOS devices that support haptic feedback, this uses the default system
20 /// vibration value (`kSystemSoundID_Vibrate`).
21 ///
22 /// On Android, this uses the platform haptic feedback API to simulate a
23 /// response to a long press (`HapticFeedbackConstants.LONG_PRESS`).
24 pub fn vibrate(app: &App) {
25 if let Some(haptics) = app.platform().haptics() {
26 haptics.feedback(HapticFeedbackType::Vibrate);
27 }
28 }
29
30 /// Provides a haptic feedback corresponding a collision impact with a light mass.
31 ///
32 /// On iOS versions 10 and above, this uses a `UIImpactFeedbackGenerator` with
33 /// `UIImpactFeedbackStyleLight`. This call has no effects on iOS versions
34 /// below 10.
35 ///
36 /// On Android, this uses `HapticFeedbackConstants.VIRTUAL_KEY`.
37 ///
38 /// See also:
39 ///
40 /// * [Human Interface Selection Playing Impact Haptic](https://developer.apple.com/design/human-interface-guidelines/playing-haptics#Impact)
41 pub fn light_impact(app: &App) {
42 if let Some(haptics) = app.platform().haptics() {
43 haptics.feedback(HapticFeedbackType::LightImpact);
44 }
45 }
46
47 /// Provides a haptic feedback corresponding a collision impact with a medium mass.
48 ///
49 /// On iOS versions 10 and above, this uses a `UIImpactFeedbackGenerator` with
50 /// `UIImpactFeedbackStyleMedium`. This call has no effects on iOS versions
51 /// below 10.
52 ///
53 /// On Android, this uses `HapticFeedbackConstants.KEYBOARD_TAP`.
54 pub fn medium_impact(app: &App) {
55 if let Some(haptics) = app.platform().haptics() {
56 haptics.feedback(HapticFeedbackType::MediumImpact);
57 }
58 }
59
60 /// Provides a haptic feedback corresponding a collision impact with a heavy mass.
61 ///
62 /// On iOS versions 10 and above, this uses a `UIImpactFeedbackGenerator` with
63 /// `UIImpactFeedbackStyleHeavy`. This call has no effects on iOS versions
64 /// below 10.
65 ///
66 /// On Android, this uses `HapticFeedbackConstants.CONTEXT_CLICK` on API levels
67 /// 23 and above. This call has no effects on Android API levels below 23.
68 pub fn heavy_impact(app: &App) {
69 if let Some(haptics) = app.platform().haptics() {
70 haptics.feedback(HapticFeedbackType::HeavyImpact);
71 }
72 }
73
74 /// Provides a haptic feedback indication selection changing through discrete values.
75 ///
76 /// On iOS versions 10 and above, this uses a `UISelectionFeedbackGenerator`.
77 /// This call has no effects on iOS versions below 10.
78 ///
79 /// On Android, this uses `HapticFeedbackConstants.CLOCK_TICK`.
80 ///
81 /// See also:
82 ///
83 /// * [Human Interface Selection Playing Selection Haptics](https://developer.apple.com/design/human-interface-guidelines/playing-haptics#Selection)
84 pub fn selection_click(app: &App) {
85 if let Some(haptics) = app.platform().haptics() {
86 haptics.feedback(HapticFeedbackType::SelectionClick);
87 }
88 }
89
90 /// Provides a haptic feedback indicating that a task or action has completed
91 /// successfully.
92 ///
93 /// On iOS, this uses a `UINotificationFeedbackGenerator` with
94 /// `UINotificationFeedbackTypeSuccess`.
95 ///
96 /// On Android, this uses `HapticFeedbackConstants.CONFIRM` on API levels 30
97 /// and above. This call has no effects on Android API levels below 30.
98 ///
99 /// See also:
100 ///
101 /// * [Human Interface Guidelines Playing Haptics](https://developer.apple.com/design/human-interface-guidelines/playing-haptics#Notification)
102 pub fn success_notification(app: &App) {
103 if let Some(haptics) = app.platform().haptics() {
104 haptics.feedback(HapticFeedbackType::SuccessNotification);
105 }
106 }
107
108 /// Provides a haptic feedback indicating that a task or action has produced
109 /// a warning.
110 ///
111 /// On iOS, this uses a `UINotificationFeedbackGenerator` with
112 /// `UINotificationFeedbackTypeWarning`.
113 ///
114 /// On Android, this uses `HapticFeedbackConstants.KEYBOARD_TAP` on API
115 /// levels 30 and above. This call has no effects on Android API levels below
116 /// 30.
117 pub fn warning_notification(app: &App) {
118 if let Some(haptics) = app.platform().haptics() {
119 haptics.feedback(HapticFeedbackType::WarningNotification);
120 }
121 }
122
123 /// Provides a haptic feedback indicating that a task or action has failed.
124 ///
125 /// On iOS, this uses a `UINotificationFeedbackGenerator` with
126 /// `UINotificationFeedbackTypeError`.
127 ///
128 /// On Android, this uses `HapticFeedbackConstants.REJECT` on API levels 30
129 /// and above. This call has no effects on Android API levels below 30.
130 pub fn error_notification(app: &App) {
131 if let Some(haptics) = app.platform().haptics() {
132 haptics.feedback(HapticFeedbackType::ErrorNotification);
133 }
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use inset_foundation::AppCell;
140 use std::cell::RefCell;
141 use std::rc::Rc;
142
143 use inset_embedder::{Haptics, TargetPlatform};
144 use inset_test::TestPlatform;
145
146 use super::*;
147
148 /// A host that records the feedback kinds it is asked for.
149 #[derive(Default)]
150 struct RecordingHaptics {
151 kinds: RefCell<Vec<HapticFeedbackType>>,
152 }
153
154 impl Haptics for RecordingHaptics {
155 fn feedback(&self, kind: HapticFeedbackType) {
156 self.kinds.borrow_mut().push(kind);
157 }
158 }
159
160 #[test]
161 fn each_call_sends_its_own_feedback_kind_to_the_platform() {
162 let haptics = Rc::new(RecordingHaptics::default());
163 let platform = TestPlatform::new()
164 .on(TargetPlatform::IOS)
165 .with_haptics(haptics.clone());
166 let cell = AppCell::with_platform(Rc::new(platform));
167 let app = cell.borrow();
168
169 HapticFeedback::vibrate(&app);
170 HapticFeedback::light_impact(&app);
171 HapticFeedback::medium_impact(&app);
172 HapticFeedback::heavy_impact(&app);
173 HapticFeedback::selection_click(&app);
174 HapticFeedback::success_notification(&app);
175 HapticFeedback::warning_notification(&app);
176 HapticFeedback::error_notification(&app);
177
178 assert_eq!(
179 haptics.kinds.borrow().as_slice(),
180 [
181 HapticFeedbackType::Vibrate,
182 HapticFeedbackType::LightImpact,
183 HapticFeedbackType::MediumImpact,
184 HapticFeedbackType::HeavyImpact,
185 HapticFeedbackType::SelectionClick,
186 HapticFeedbackType::SuccessNotification,
187 HapticFeedbackType::WarningNotification,
188 HapticFeedbackType::ErrorNotification,
189 ]
190 );
191 }
192}