mirror of
https://github.com/selfxyz/self.git
synced 2026-04-05 03:00:53 -04:00
SEL-178: Improve haptic feedback library (#535)
* fix dev settings typing * add dev screens file * save haptic feedback progress * change ordedr * fix initial route and add haptic feedback screen to dev settings options
This commit is contained in:
25
app/src/Navigation/dev.ts
Normal file
25
app/src/Navigation/dev.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { NativeStackNavigationOptions } from '@react-navigation/native-stack';
|
||||
|
||||
import DevHapticFeedbackScreen from '../screens/Settings/DevHapticFeedback';
|
||||
import DevSettingsScreen from '../screens/Settings/DevSettingsScreen';
|
||||
import { white } from '../utils/colors';
|
||||
|
||||
const settingsScreens = {
|
||||
DevSettings: {
|
||||
screen: DevSettingsScreen,
|
||||
options: {
|
||||
title: 'Developer Settings',
|
||||
headerStyle: {
|
||||
backgroundColor: white,
|
||||
},
|
||||
} as NativeStackNavigationOptions,
|
||||
},
|
||||
DevHapticFeedback: {
|
||||
screen: DevHapticFeedbackScreen,
|
||||
options: {
|
||||
title: 'Haptic Feedback',
|
||||
} as NativeStackNavigationOptions,
|
||||
},
|
||||
};
|
||||
|
||||
export default settingsScreens;
|
||||
@@ -16,6 +16,7 @@ import { white } from '../utils/colors';
|
||||
import { setupUniversalLinkListenerInNavigation } from '../utils/deeplinks';
|
||||
import accountScreens from './account';
|
||||
import aesopScreens from './aesop';
|
||||
import devScreens from './dev';
|
||||
import homeScreens from './home';
|
||||
import passportScreens from './passport';
|
||||
import proveScreens from './prove';
|
||||
@@ -39,6 +40,7 @@ const AppNavigation = createNativeStackNavigator({
|
||||
...accountScreens,
|
||||
...settingsScreens,
|
||||
...recoveryScreens,
|
||||
...devScreens,
|
||||
...aesopScreens,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { NativeStackNavigationOptions } from '@react-navigation/native-stack';
|
||||
|
||||
import CloudBackupScreen from '../screens/Settings/CloudBackupScreen';
|
||||
import DevSettingsScreen from '../screens/Settings/DevSettingsScreen';
|
||||
import PassportDataInfoScreen from '../screens/Settings/PassportDataInfoScreen';
|
||||
import ShowRecoveryPhraseScreen from '../screens/Settings/ShowRecoveryPhraseScreen';
|
||||
import SettingsScreen from '../screens/SettingsScreen';
|
||||
@@ -43,15 +42,6 @@ const settingsScreens = {
|
||||
},
|
||||
} as NativeStackNavigationOptions,
|
||||
},
|
||||
DevSettings: {
|
||||
screen: DevSettingsScreen,
|
||||
options: {
|
||||
title: 'Developer Settings',
|
||||
headerStyle: {
|
||||
backgroundColor: white,
|
||||
},
|
||||
} as NativeStackNavigationOptions,
|
||||
},
|
||||
CloudBackupSettings: {
|
||||
screen: CloudBackupScreen,
|
||||
options: {
|
||||
|
||||
61
app/src/screens/Settings/DevHapticFeedback.tsx
Normal file
61
app/src/screens/Settings/DevHapticFeedback.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { Button, ScrollView, styled } from 'tamagui';
|
||||
|
||||
import {
|
||||
feedbackProgress,
|
||||
feedbackSuccess,
|
||||
feedbackUnsuccessful,
|
||||
impactLight,
|
||||
impactMedium,
|
||||
notificationError,
|
||||
notificationSuccess,
|
||||
notificationWarning,
|
||||
selectionChange,
|
||||
} from '../../utils/haptic';
|
||||
|
||||
const StyledButton = styled(Button, {
|
||||
width: '75%',
|
||||
marginHorizontal: 'auto',
|
||||
padding: 10,
|
||||
backgroundColor: '#007BFF',
|
||||
borderRadius: 10,
|
||||
marginVertical: 10,
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
});
|
||||
|
||||
const DevHapticFeedback = () => {
|
||||
return (
|
||||
<ScrollView style={styles.container}>
|
||||
<StyledButton onPress={feedbackUnsuccessful}>
|
||||
Feedback Unsuccessful
|
||||
</StyledButton>
|
||||
<StyledButton onPress={feedbackSuccess}>Feedback Success</StyledButton>
|
||||
<StyledButton onPress={feedbackProgress}>Feedback Progress</StyledButton>
|
||||
<StyledButton onPress={notificationError}>
|
||||
Notification Error
|
||||
</StyledButton>
|
||||
<StyledButton onPress={notificationSuccess}>
|
||||
Notification Success
|
||||
</StyledButton>
|
||||
<StyledButton onPress={notificationWarning}>
|
||||
Notification Warning
|
||||
</StyledButton>
|
||||
<StyledButton onPress={impactLight}>Impact Light</StyledButton>
|
||||
<StyledButton onPress={impactMedium}>Impact Medium</StyledButton>
|
||||
<StyledButton onPress={selectionChange}>Selection Change</StyledButton>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingVertical: 50,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
});
|
||||
|
||||
export default DevHapticFeedback;
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
VenetianMask,
|
||||
} from '@tamagui/lucide-icons';
|
||||
import React, { PropsWithChildren, useEffect, useState } from 'react';
|
||||
import { Platform, TextInput } from 'react-native';
|
||||
import { Platform, StyleProp, TextInput } from 'react-native';
|
||||
import {
|
||||
Adapt,
|
||||
Button,
|
||||
@@ -31,9 +31,22 @@ import {
|
||||
} from '../../stores/passportDataProvider';
|
||||
import { borderColor, textBlack } from '../../utils/colors';
|
||||
|
||||
interface DevSettingsScreenProps {}
|
||||
interface DevSettingsScreenProps extends PropsWithChildren {
|
||||
color?: string;
|
||||
width?: number;
|
||||
justifyContent?:
|
||||
| 'center'
|
||||
| 'unset'
|
||||
| 'flex-start'
|
||||
| 'flex-end'
|
||||
| 'space-between'
|
||||
| 'space-around'
|
||||
| 'space-evenly';
|
||||
userSelect?: 'all' | 'text' | 'none' | 'contain';
|
||||
style?: StyleProp<any>;
|
||||
}
|
||||
|
||||
function SelectableText({ children, ...props }: PropsWithChildren) {
|
||||
function SelectableText({ children, ...props }: DevSettingsScreenProps) {
|
||||
if (Platform.OS === 'ios') {
|
||||
return (
|
||||
<TextInput multiline editable={false} {...props}>
|
||||
@@ -51,6 +64,7 @@ function SelectableText({ children, ...props }: PropsWithChildren) {
|
||||
|
||||
const items = [
|
||||
'DevSettings',
|
||||
'DevHapticFeedback',
|
||||
'Splash',
|
||||
'Launch',
|
||||
'PassportOnboarding',
|
||||
@@ -80,8 +94,7 @@ const ScreenSelector = ({}) => {
|
||||
const navigation = useNavigation();
|
||||
return (
|
||||
<Select
|
||||
onValueChange={screen => {
|
||||
// @ts-expect-error - weird typing?
|
||||
onValueChange={(screen: any) => {
|
||||
navigation.navigate(screen);
|
||||
}}
|
||||
disablePreventBodyScroll
|
||||
|
||||
@@ -13,13 +13,15 @@ export type HapticType =
|
||||
export type HapticOptions = {
|
||||
enableVibrateFallback?: boolean;
|
||||
ignoreAndroidSystemSettings?: boolean;
|
||||
androidPattern?: number[];
|
||||
pattern?: number[];
|
||||
increaseIosIntensity?: boolean;
|
||||
};
|
||||
|
||||
const defaultOptions: HapticOptions = {
|
||||
enableVibrateFallback: true,
|
||||
ignoreAndroidSystemSettings: false,
|
||||
androidPattern: [50, 100, 50],
|
||||
pattern: [50, 100, 50],
|
||||
increaseIosIntensity: true,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -35,31 +37,111 @@ export const buttonTap = impactLight;
|
||||
export const cancelTap = selectionChange;
|
||||
export const confirmTap = impactMedium;
|
||||
|
||||
// Custom feedback events
|
||||
// consistent light feedback at a steady interval
|
||||
export const feedbackProgress = () => {
|
||||
if (Platform.OS === 'android') {
|
||||
triggerFeedback('custom', {
|
||||
pattern: [0, 50, 450, 50, 450, 50],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
triggerFeedback('impactLight', {
|
||||
increaseIosIntensity: false,
|
||||
});
|
||||
}, 500);
|
||||
setTimeout(() => {
|
||||
triggerFeedback('impactLight', {
|
||||
increaseIosIntensity: false,
|
||||
});
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
triggerFeedback('impactLight', {
|
||||
increaseIosIntensity: false,
|
||||
});
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
// light -> medium -> heavy intensity in sequence
|
||||
export const feedbackSuccess = () => {
|
||||
if (Platform.OS === 'android') {
|
||||
triggerFeedback('custom', {
|
||||
pattern: [500, 50, 200, 100, 150, 150],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
triggerFeedback('impactLight', {
|
||||
increaseIosIntensity: false,
|
||||
});
|
||||
}, 500);
|
||||
setTimeout(() => {
|
||||
triggerFeedback('impactMedium', {
|
||||
increaseIosIntensity: false,
|
||||
});
|
||||
}, 750);
|
||||
setTimeout(() => {
|
||||
triggerFeedback('impactHeavy', {
|
||||
increaseIosIntensity: false,
|
||||
});
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
// heavy -> medium -> light intensity in sequence
|
||||
export const feedbackUnsuccessful = () => {
|
||||
if (Platform.OS === 'android') {
|
||||
triggerFeedback('custom', {
|
||||
pattern: [500, 150, 100, 100, 150, 50],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
triggerFeedback('impactHeavy', {
|
||||
increaseIosIntensity: false,
|
||||
});
|
||||
}, 500);
|
||||
setTimeout(() => {
|
||||
triggerFeedback('impactMedium', {
|
||||
increaseIosIntensity: false,
|
||||
});
|
||||
}, 750);
|
||||
setTimeout(() => {
|
||||
triggerFeedback('impactLight', {
|
||||
increaseIosIntensity: false,
|
||||
});
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggers haptic feedback or vibration based on platform.
|
||||
* @param type - The haptic feedback type.
|
||||
* @param options - Custom options (optional).
|
||||
*/
|
||||
export const triggerFeedback = (
|
||||
type: HapticType,
|
||||
type: HapticType | 'custom',
|
||||
options: HapticOptions = {},
|
||||
) => {
|
||||
const mergedOptions = { ...defaultOptions, ...options };
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
// increase feedback intensity for iOS
|
||||
if (type === 'impactLight') {
|
||||
type = 'impactMedium';
|
||||
} else if (type === 'impactMedium') {
|
||||
type = 'impactHeavy';
|
||||
if (Platform.OS === 'ios' && type !== 'custom') {
|
||||
if (mergedOptions.increaseIosIntensity) {
|
||||
if (type === 'impactLight') {
|
||||
type = 'impactMedium';
|
||||
} else if (type === 'impactMedium') {
|
||||
type = 'impactHeavy';
|
||||
}
|
||||
}
|
||||
|
||||
ReactNativeHapticFeedback.trigger(type, {
|
||||
enableVibrateFallback: mergedOptions.enableVibrateFallback,
|
||||
ignoreAndroidSystemSettings: mergedOptions.ignoreAndroidSystemSettings,
|
||||
});
|
||||
} else {
|
||||
if (mergedOptions.androidPattern) {
|
||||
Vibration.vibrate(mergedOptions.androidPattern, false);
|
||||
if (mergedOptions.pattern) {
|
||||
Vibration.vibrate(mergedOptions.pattern, false);
|
||||
} else {
|
||||
Vibration.vibrate(100);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user