Skip to main content

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}