refactor(rebreak-native): migrate device sheets to FormSheet, delete KeyboardAwareSheet (phase 3C)
AddMacSheet + AddWindowsSheet now use FormSheet instead of the old KeyboardAwareSheet. Steps with no TextInput disable growWithKeyboard; Step 2 (long onboarding list) gets an internal ScrollView so content is scrollable within the sheet cap. Sheet heights converted from fixed px to initialHeightPct fractions. KeyboardAwareSheet.tsx deleted — no remaining consumers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f24c364c81
commit
3eaf3f098a
@ -1,224 +0,0 @@
|
|||||||
import { ReactNode, useEffect, useRef, useState } from 'react';
|
|
||||||
import {
|
|
||||||
Animated,
|
|
||||||
Dimensions,
|
|
||||||
Easing,
|
|
||||||
Keyboard,
|
|
||||||
Modal,
|
|
||||||
Platform,
|
|
||||||
TouchableOpacity,
|
|
||||||
StyleProp,
|
|
||||||
View,
|
|
||||||
ViewStyle,
|
|
||||||
} from 'react-native';
|
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
||||||
import { useColors } from '../lib/theme';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Universal-Bottom-Sheet für Forms mit TextInput.
|
|
||||||
*
|
|
||||||
* Pattern (verifiziert auf PostCommentsSheet + EditMailAccountSheet):
|
|
||||||
*
|
|
||||||
* 1. Outer-Animated.View hat animated `height` (JS-driver) — Sheet WÄCHST
|
|
||||||
* bei Tastatur-Open um genau die Tastatur-Höhe.
|
|
||||||
* 2. Inner-Animated.View hat `transform: translateY` (Native-driver) —
|
|
||||||
* Slide-In/Out smooth. Driver-Mix-Trennung verhindert
|
|
||||||
* "Style property 'height' is not supported by native animated module"-Crash.
|
|
||||||
* 3. iOS: `paddingBottom: keyboardHeight` shifted Form innerhalb des
|
|
||||||
* gewachsenen Sheets über die Tastatur. Android: `windowSoftInputMode=adjustResize`
|
|
||||||
* im Manifest schrumpft das Window selbst.
|
|
||||||
* 4. Flex-Spacer drückt `children` (Form) automatisch an den Sheet-Bottom-Edge —
|
|
||||||
* sitzt direkt über der Tastatur ohne Gap.
|
|
||||||
*
|
|
||||||
* Anti-Pattern (siehe `docs/internal/RECOVERY_LOG_2026-05-10.md` §7.2):
|
|
||||||
* - `useKeyboardAnimation()` aus `react-native-keyboard-controller` liefert
|
|
||||||
* in iOS-Modals keine Höhe (separate UIWindow). Hier: plain RN
|
|
||||||
* `Keyboard.addListener` für die Höhe.
|
|
||||||
* - `Animated.subtract`/`marginBottom: keyboardHeight` mischen JS+Native-Driver
|
|
||||||
* auf demselben View → Bouncing oder Crash.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* ```tsx
|
|
||||||
* <KeyboardAwareSheet
|
|
||||||
* visible={visible}
|
|
||||||
* onClose={onClose}
|
|
||||||
* collapsedHeight={280}
|
|
||||||
* header={<HeaderRow title="Passwort" onCancel={onClose} />}
|
|
||||||
* >
|
|
||||||
* <View style={{ padding: 20, gap: 14 }}>
|
|
||||||
* <TextInput ... />
|
|
||||||
* <SaveButton ... />
|
|
||||||
* </View>
|
|
||||||
* </KeyboardAwareSheet>
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export interface KeyboardAwareSheetProps {
|
|
||||||
visible: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
/** Sheet-Höhe wenn Tastatur zu. Eng auf Inhalt zuschneiden — typisch 220-340px. */
|
|
||||||
collapsedHeight: number;
|
|
||||||
/** Optionaler Header (Cancel/Title-Row). Rendert direkt unter dem Drag-Handle. */
|
|
||||||
header?: ReactNode;
|
|
||||||
/** Form-Inhalt. Wird per Flex-Spacer an den Sheet-Bottom gedrückt — sitzt
|
|
||||||
* damit direkt über der Tastatur sobald die offen ist. */
|
|
||||||
children: ReactNode;
|
|
||||||
/** Default true — Tap auf Backdrop schließt das Sheet. */
|
|
||||||
dismissOnBackdrop?: boolean;
|
|
||||||
/** Default true — kleiner Drag-Handle ganz oben am Sheet. */
|
|
||||||
showDragHandle?: boolean;
|
|
||||||
/** Default true — fügt unten eine Safe-Area-Spacer-Höhe ein (insets.bottom). */
|
|
||||||
showSafeAreaSpacer?: boolean;
|
|
||||||
/** Default true — interner Flex-Spacer drückt children zum Sheet-Bottom.
|
|
||||||
* Auf false setzen wenn der Inhalt seine eigene Scroll-/Flex-Logik hat
|
|
||||||
* (z.B. ScrollView mit Provider-Grid, Listen). */
|
|
||||||
pushChildrenToBottom?: boolean;
|
|
||||||
/** Border-Radius oben. Default 20. */
|
|
||||||
topRadius?: number;
|
|
||||||
/** Optional zusätzlicher Style für den Sheet-Container. */
|
|
||||||
containerStyle?: StyleProp<ViewStyle>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function KeyboardAwareSheet({
|
|
||||||
visible,
|
|
||||||
onClose,
|
|
||||||
collapsedHeight,
|
|
||||||
header,
|
|
||||||
children,
|
|
||||||
dismissOnBackdrop = true,
|
|
||||||
showDragHandle = true,
|
|
||||||
showSafeAreaSpacer = true,
|
|
||||||
pushChildrenToBottom = true,
|
|
||||||
topRadius = 20,
|
|
||||||
containerStyle,
|
|
||||||
}: KeyboardAwareSheetProps) {
|
|
||||||
const colors = useColors();
|
|
||||||
const insets = useSafeAreaInsets();
|
|
||||||
|
|
||||||
const slideY = useRef(new Animated.Value(collapsedHeight)).current;
|
|
||||||
const backdropOpacity = useRef(new Animated.Value(0)).current;
|
|
||||||
const sheetHeight = useRef(new Animated.Value(collapsedHeight)).current;
|
|
||||||
const [keyboardHeight, setKeyboardHeight] = useState(0);
|
|
||||||
|
|
||||||
// Slide-In + Backdrop-Fade bei `visible=true`
|
|
||||||
useEffect(() => {
|
|
||||||
if (visible) {
|
|
||||||
slideY.setValue(collapsedHeight);
|
|
||||||
backdropOpacity.setValue(0);
|
|
||||||
Animated.parallel([
|
|
||||||
Animated.timing(slideY, {
|
|
||||||
toValue: 0,
|
|
||||||
duration: 280,
|
|
||||||
easing: Easing.out(Easing.cubic),
|
|
||||||
useNativeDriver: true,
|
|
||||||
}),
|
|
||||||
Animated.timing(backdropOpacity, {
|
|
||||||
toValue: 1,
|
|
||||||
duration: 220,
|
|
||||||
useNativeDriver: true,
|
|
||||||
}),
|
|
||||||
]).start();
|
|
||||||
}
|
|
||||||
}, [visible, slideY, backdropOpacity, collapsedHeight]);
|
|
||||||
|
|
||||||
// Sheet-Höhe wächst/schrumpft mit Tastatur
|
|
||||||
useEffect(() => {
|
|
||||||
const maxHeight = Dimensions.get('window').height - insets.top - 20;
|
|
||||||
const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
|
|
||||||
const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
|
|
||||||
const showSub = Keyboard.addListener(showEvent, (e) => {
|
|
||||||
const h = e.endCoordinates.height;
|
|
||||||
setKeyboardHeight(h);
|
|
||||||
Animated.timing(sheetHeight, {
|
|
||||||
toValue: Math.min(collapsedHeight + h, maxHeight),
|
|
||||||
duration: Platform.OS === 'ios' ? (e.duration ?? 250) : 220,
|
|
||||||
easing: Easing.out(Easing.cubic),
|
|
||||||
useNativeDriver: false,
|
|
||||||
}).start();
|
|
||||||
});
|
|
||||||
const hideSub = Keyboard.addListener(hideEvent, (e) => {
|
|
||||||
setKeyboardHeight(0);
|
|
||||||
Animated.timing(sheetHeight, {
|
|
||||||
toValue: collapsedHeight,
|
|
||||||
duration: Platform.OS === 'ios' ? (e?.duration ?? 250) : 220,
|
|
||||||
easing: Easing.out(Easing.cubic),
|
|
||||||
useNativeDriver: false,
|
|
||||||
}).start();
|
|
||||||
});
|
|
||||||
return () => {
|
|
||||||
showSub.remove();
|
|
||||||
hideSub.remove();
|
|
||||||
};
|
|
||||||
}, [sheetHeight, collapsedHeight, insets.top]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal visible={visible} transparent animationType="none" onRequestClose={onClose}>
|
|
||||||
{/* Backdrop */}
|
|
||||||
<Animated.View
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
backgroundColor: 'rgba(0,0,0,0.4)',
|
|
||||||
opacity: backdropOpacity,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{dismissOnBackdrop && <TouchableOpacity activeOpacity={1} style={{ flex: 1 }} onPress={onClose} />}
|
|
||||||
</Animated.View>
|
|
||||||
|
|
||||||
{/* Outer: animated height (JS-driver) */}
|
|
||||||
<Animated.View
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
height: sheetHeight,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Inner: animated transform (Native-driver). Driver-Mix vermeiden
|
|
||||||
durch zwei verschachtelte Animated.Views. */}
|
|
||||||
<Animated.View
|
|
||||||
style={[
|
|
||||||
{
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: colors.bg,
|
|
||||||
borderTopLeftRadius: topRadius,
|
|
||||||
borderTopRightRadius: topRadius,
|
|
||||||
transform: [{ translateY: slideY }],
|
|
||||||
paddingBottom: Platform.OS === 'ios' ? keyboardHeight : 0,
|
|
||||||
},
|
|
||||||
containerStyle,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<View style={{ flex: 1 }}>
|
|
||||||
{showDragHandle && (
|
|
||||||
<View style={{ alignItems: 'center', paddingTop: 8, paddingBottom: 4 }}>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
width: 36,
|
|
||||||
height: 4,
|
|
||||||
borderRadius: 2,
|
|
||||||
backgroundColor: colors.border,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
{header}
|
|
||||||
{pushChildrenToBottom ? (
|
|
||||||
<>
|
|
||||||
{/* Flex-Spacer drückt children an den Sheet-Bottom */}
|
|
||||||
<View style={{ flex: 1 }} />
|
|
||||||
{children}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<View style={{ flex: 1 }}>{children}</View>
|
|
||||||
)}
|
|
||||||
{showSafeAreaSpacer && <View style={{ height: insets.bottom }} />}
|
|
||||||
</View>
|
|
||||||
</Animated.View>
|
|
||||||
</Animated.View>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -2,6 +2,7 @@ import {
|
|||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Alert,
|
Alert,
|
||||||
Linking,
|
Linking,
|
||||||
|
ScrollView,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
@ -12,7 +13,7 @@ import { Ionicons } from '@expo/vector-icons';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import * as Haptics from 'expo-haptics';
|
import * as Haptics from 'expo-haptics';
|
||||||
import { useColors } from '../../lib/theme';
|
import { useColors } from '../../lib/theme';
|
||||||
import { KeyboardAwareSheet } from '../KeyboardAwareSheet';
|
import { FormSheet } from '../FormSheet';
|
||||||
import { RiveAvatar } from '../RiveAvatar';
|
import { RiveAvatar } from '../RiveAvatar';
|
||||||
import { useProtectedDevicesStore } from '../../stores/protectedDevices';
|
import { useProtectedDevicesStore } from '../../stores/protectedDevices';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
@ -109,70 +110,52 @@ export function AddMacSheet({
|
|||||||
router.push('/coach');
|
router.push('/coach');
|
||||||
}
|
}
|
||||||
|
|
||||||
const collapsedHeight = step === 1 ? 300 : step === 2 ? 620 : 380;
|
const sheetTitle =
|
||||||
|
step === 1
|
||||||
|
? t('devices.label_question')
|
||||||
|
: step === 2
|
||||||
|
? t('devices.download_button')
|
||||||
|
: t('devices.success_title');
|
||||||
|
|
||||||
|
const initialHeightPct = step === 1 ? 0.42 : step === 2 ? 0.74 : 0.52;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAwareSheet
|
<FormSheet
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
collapsedHeight={collapsedHeight}
|
title={sheetTitle}
|
||||||
pushChildrenToBottom={false}
|
initialHeightPct={initialHeightPct}
|
||||||
header={
|
growWithKeyboard={step === 1}
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
paddingHorizontal: 20,
|
|
||||||
paddingVertical: 12,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
fontSize: 17,
|
|
||||||
color: colors.text,
|
|
||||||
fontFamily: 'Nunito_700Bold',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{step === 1
|
|
||||||
? t('devices.label_question')
|
|
||||||
: step === 2
|
|
||||||
? t('devices.download_button')
|
|
||||||
: t('devices.success_title')}
|
|
||||||
</Text>
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={handleClose}
|
|
||||||
hitSlop={8}
|
|
||||||
activeOpacity={0.5}
|
|
||||||
>
|
|
||||||
<Ionicons name="close" size={22} color={colors.textMuted} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{step === 1 && <Step1LabelContent
|
{step === 1 && (
|
||||||
label={label}
|
<Step1LabelContent
|
||||||
setLabel={setLabel}
|
label={label}
|
||||||
labelError={labelError}
|
setLabel={setLabel}
|
||||||
onPrepare={handlePrepare}
|
labelError={labelError}
|
||||||
enrolling={enrolling}
|
onPrepare={handlePrepare}
|
||||||
colors={colors}
|
enrolling={enrolling}
|
||||||
t={t}
|
colors={colors}
|
||||||
/>}
|
t={t}
|
||||||
{step === 2 && <Step2OnboardingContent
|
/>
|
||||||
onDownload={handleDownload}
|
)}
|
||||||
onConfirmInstalled={handleConfirmInstalled}
|
{step === 2 && (
|
||||||
onNeedHelp={handleNeedHelp}
|
<Step2OnboardingContent
|
||||||
confirming={confirming}
|
onDownload={handleDownload}
|
||||||
colors={colors}
|
onConfirmInstalled={handleConfirmInstalled}
|
||||||
t={t}
|
onNeedHelp={handleNeedHelp}
|
||||||
/>}
|
confirming={confirming}
|
||||||
{step === 3 && <Step3SuccessContent
|
colors={colors}
|
||||||
onClose={handleClose}
|
t={t}
|
||||||
colors={colors}
|
/>
|
||||||
t={t}
|
)}
|
||||||
/>}
|
{step === 3 && (
|
||||||
</KeyboardAwareSheet>
|
<Step3SuccessContent
|
||||||
|
onClose={handleClose}
|
||||||
|
colors={colors}
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</FormSheet>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,7 +244,12 @@ function Step2OnboardingContent({
|
|||||||
t: (k: string) => string;
|
t: (k: string) => string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<View style={{ paddingHorizontal: 20, paddingTop: 4, paddingBottom: 16, gap: 16 }}>
|
<ScrollView
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
contentContainerStyle={{ paddingHorizontal: 20, paddingTop: 4, paddingBottom: 16, gap: 16 }}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
|
>
|
||||||
{/* Lyra intro card */}
|
{/* Lyra intro card */}
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@ -390,7 +378,7 @@ function Step2OnboardingContent({
|
|||||||
{t('devices.need_help')}
|
{t('devices.need_help')}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import {
|
|||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Alert,
|
Alert,
|
||||||
Linking,
|
Linking,
|
||||||
|
ScrollView,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
@ -12,7 +13,7 @@ import { Ionicons } from '@expo/vector-icons';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import * as Haptics from 'expo-haptics';
|
import * as Haptics from 'expo-haptics';
|
||||||
import { useColors } from '../../lib/theme';
|
import { useColors } from '../../lib/theme';
|
||||||
import { KeyboardAwareSheet } from '../KeyboardAwareSheet';
|
import { FormSheet } from '../FormSheet';
|
||||||
import { RiveAvatar } from '../RiveAvatar';
|
import { RiveAvatar } from '../RiveAvatar';
|
||||||
import { useProtectedDevicesStore } from '../../stores/protectedDevices';
|
import { useProtectedDevicesStore } from '../../stores/protectedDevices';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
@ -110,46 +111,22 @@ export function AddWindowsSheet({
|
|||||||
router.push('/coach');
|
router.push('/coach');
|
||||||
}
|
}
|
||||||
|
|
||||||
const collapsedHeight = step === 1 ? 300 : step === 2 ? 700 : 380;
|
const sheetTitle =
|
||||||
|
step === 1
|
||||||
|
? t('devices.windows_label_question')
|
||||||
|
: step === 2
|
||||||
|
? t('devices.windows_download_button')
|
||||||
|
: t('devices.windows_success_title');
|
||||||
|
|
||||||
|
const initialHeightPct = step === 1 ? 0.42 : step === 2 ? 0.74 : 0.52;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAwareSheet
|
<FormSheet
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
collapsedHeight={collapsedHeight}
|
title={sheetTitle}
|
||||||
pushChildrenToBottom={false}
|
initialHeightPct={initialHeightPct}
|
||||||
header={
|
growWithKeyboard={step === 1}
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
paddingHorizontal: 20,
|
|
||||||
paddingVertical: 12,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
fontSize: 17,
|
|
||||||
color: colors.text,
|
|
||||||
fontFamily: 'Nunito_700Bold',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{step === 1
|
|
||||||
? t('devices.windows_label_question')
|
|
||||||
: step === 2
|
|
||||||
? t('devices.windows_download_button')
|
|
||||||
: t('devices.windows_success_title')}
|
|
||||||
</Text>
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={handleClose}
|
|
||||||
hitSlop={8}
|
|
||||||
activeOpacity={0.5}
|
|
||||||
>
|
|
||||||
<Ionicons name="close" size={22} color={colors.textMuted} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{step === 1 && (
|
{step === 1 && (
|
||||||
<WindowsStep1LabelContent
|
<WindowsStep1LabelContent
|
||||||
@ -179,7 +156,7 @@ export function AddWindowsSheet({
|
|||||||
t={t}
|
t={t}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</KeyboardAwareSheet>
|
</FormSheet>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,7 +245,12 @@ function WindowsStep2OnboardingContent({
|
|||||||
t: (k: string) => string;
|
t: (k: string) => string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<View style={{ paddingHorizontal: 20, paddingTop: 4, paddingBottom: 16, gap: 16 }}>
|
<ScrollView
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
contentContainerStyle={{ paddingHorizontal: 20, paddingTop: 4, paddingBottom: 16, gap: 16 }}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
|
>
|
||||||
{/* Lyra intro card */}
|
{/* Lyra intro card */}
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@ -397,7 +379,7 @@ function WindowsStep2OnboardingContent({
|
|||||||
{t('devices.need_help')}
|
{t('devices.need_help')}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user