Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = function (api) {
{
root: ['./'],
alias: {
'^react-native$': './shims/react-native',
'@': './',
'@assets': './assets',
'@src': './src',
Expand Down
134 changes: 134 additions & 0 deletions shims/react-native.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import React, { forwardRef } from 'react';
import type { ComponentProps, ComponentRef } from 'react';
import type { StyleProp, TextStyle } from 'react-native/index';
import * as ReactNative from 'react-native/index';

const NativeText = ReactNative.Text;
const NativeTextInput = ReactNative.TextInput;

const isAndroid = ReactNative.Platform.OS === 'android';
const ANDROID_TEXT_STYLE = { includeFontPadding: false as const };

function mergeAndroidTextStyle(style: StyleProp<TextStyle>): StyleProp<TextStyle> {
if (!isAndroid) return style;
if (style == null) return ANDROID_TEXT_STYLE;
return [ANDROID_TEXT_STYLE, style];
}

const PatchedText = forwardRef<
ComponentRef<typeof NativeText>,
ComponentProps<typeof NativeText>
>(function PatchedText(props, ref) {
const { style, allowFontScaling, ...rest } = props;
return (
<NativeText
{...rest}
ref={ref}
style={mergeAndroidTextStyle(style)}
allowFontScaling={allowFontScaling ?? false}
/>
);
});
PatchedText.displayName = 'Text';
export const Text = PatchedText as unknown as typeof ReactNative.Text;

const PatchedTextInput = forwardRef<
ComponentRef<typeof NativeTextInput>,
ComponentProps<typeof NativeTextInput>
>(function PatchedTextInput(props, ref) {
const { style, allowFontScaling, ...rest } = props;
return (
<NativeTextInput
{...rest}
ref={ref}
style={mergeAndroidTextStyle(style)}
allowFontScaling={allowFontScaling ?? false}
/>
);
});
PatchedTextInput.displayName = 'TextInput';
export const TextInput = PatchedTextInput as unknown as typeof ReactNative.TextInput;

const RN = ReactNative as typeof ReactNative & Record<string, unknown>;

export const ActivityIndicator = ReactNative.ActivityIndicator;
export const Button = ReactNative.Button;
export const DrawerLayoutAndroid = ReactNative.DrawerLayoutAndroid;
export const FlatList = ReactNative.FlatList;
export const Image = ReactNative.Image;
export const ImageBackground = ReactNative.ImageBackground;
export const InputAccessoryView = ReactNative.InputAccessoryView;
export const KeyboardAvoidingView = ReactNative.KeyboardAvoidingView;
export const experimental_LayoutConformance = ReactNative.experimental_LayoutConformance;
export const Modal = ReactNative.Modal;
export const Pressable = ReactNative.Pressable;
export const RefreshControl = ReactNative.RefreshControl;
export const ScrollView = ReactNative.ScrollView;
export const SectionList = ReactNative.SectionList;
export const StatusBar = ReactNative.StatusBar;
export const Switch = ReactNative.Switch;
export const unstable_TextAncestorContext = ReactNative.unstable_TextAncestorContext;
export const Touchable = ReactNative.Touchable;
export const TouchableHighlight = ReactNative.TouchableHighlight;
export const TouchableNativeFeedback = ReactNative.TouchableNativeFeedback;
export const TouchableOpacity = ReactNative.TouchableOpacity;
export const TouchableWithoutFeedback = ReactNative.TouchableWithoutFeedback;
export const View = ReactNative.View;
export const VirtualizedList = ReactNative.VirtualizedList;
export const VirtualizedSectionList = RN.VirtualizedSectionList;
export const unstable_VirtualView = RN.unstable_VirtualView;
export const AccessibilityInfo = ReactNative.AccessibilityInfo;
export const ActionSheetIOS = ReactNative.ActionSheetIOS;
export const Alert = ReactNative.Alert;
export const Animated = ReactNative.Animated;
export const Appearance = ReactNative.Appearance;
export const AppRegistry = ReactNative.AppRegistry;
export const AppState = ReactNative.AppState;
export const BackHandler = ReactNative.BackHandler;
export const codegenNativeCommands = ReactNative.codegenNativeCommands;
export const codegenNativeComponent = ReactNative.codegenNativeComponent;
export const DeviceEventEmitter = ReactNative.DeviceEventEmitter;
export const DeviceInfo = RN.DeviceInfo;
export const DevMenu = ReactNative.DevMenu;
export const DevSettings = ReactNative.DevSettings;
export const Dimensions = ReactNative.Dimensions;
export const DynamicColorIOS = ReactNative.DynamicColorIOS;
export const Easing = ReactNative.Easing;
export const findNodeHandle = ReactNative.findNodeHandle;
export const I18nManager = ReactNative.I18nManager;
export const InteractionManager = ReactNative.InteractionManager;
export const Keyboard = ReactNative.Keyboard;
export const LayoutAnimation = ReactNative.LayoutAnimation;
export const Linking = ReactNative.Linking;
export const LogBox = ReactNative.LogBox;
export const NativeAppEventEmitter = ReactNative.NativeAppEventEmitter;
export const NativeDialogManagerAndroid = RN.NativeDialogManagerAndroid;
export const NativeEventEmitter = ReactNative.NativeEventEmitter;
export const NativeModules = ReactNative.NativeModules;
export const Networking = RN.Networking;
export const PanResponder = ReactNative.PanResponder;
export const PermissionsAndroid = ReactNative.PermissionsAndroid;
export const PixelRatio = ReactNative.PixelRatio;
export const Platform = ReactNative.Platform;
export const PlatformColor = ReactNative.PlatformColor;
export const processColor = ReactNative.processColor;
export const registerCallableModule = ReactNative.registerCallableModule;
export const requireNativeComponent = ReactNative.requireNativeComponent;
export const RootTagContext = ReactNative.RootTagContext;
export const Settings = ReactNative.Settings;
export const Share = ReactNative.Share;
export const StyleSheet = ReactNative.StyleSheet;
export const Systrace = ReactNative.Systrace;
export const ToastAndroid = ReactNative.ToastAndroid;
export const TurboModuleRegistry = ReactNative.TurboModuleRegistry;
export const UIManager = ReactNative.UIManager;
export const unstable_batchedUpdates = ReactNative.unstable_batchedUpdates;
export const useAnimatedValue = ReactNative.useAnimatedValue;
export const useColorScheme = ReactNative.useColorScheme;
export const useWindowDimensions = ReactNative.useWindowDimensions;
export const UTFSequence = RN.UTFSequence;
export const Vibration = ReactNative.Vibration;
export const VirtualViewMode = RN.VirtualViewMode;

/** 공식 타입 정의에 있는 타입들을 보냄으로써 ViewProps 같은 타입도 쓸 수 있도록 확장 */
export type * from 'react-native/index';
11 changes: 9 additions & 2 deletions src/components/AppTextInput/AppTextInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { forwardRef, useRef, useEffect, useImperativeHandle, useMemo } from 'react';
import {
forwardRef,
useRef,
useEffect,
useImperativeHandle,
useMemo,
type ComponentRef,
} from 'react';
import { Platform, StyleSheet, TextInput, TextInputProps, TextStyle } from 'react-native';

type WebTextStyle = TextStyle & {
Expand Down Expand Up @@ -31,7 +38,7 @@ const AppTextInput = forwardRef<AppTextInputRef, TextInputProps>(

const { fontFamily, fontSize, letterSpacing, lineHeight } = flattenedStyle;

const internalRef = useRef<TextInput>(null);
const internalRef = useRef<ComponentRef<typeof TextInput>>(null);

useImperativeHandle(ref, () => ({
focus: () => internalRef.current?.focus(),
Expand Down
80 changes: 43 additions & 37 deletions src/components/RouteDetailMap/RouteDetailMap.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { View, ViewProps } from 'react-native';
import { Platform, View, ViewProps } from 'react-native';
import React, { useMemo } from 'react';
import { NativeViewGestureHandler } from 'react-native-gesture-handler';
import Mapbox, { Camera, MapView, ShapeSource, LineLayer } from '@rnmapbox/maps';
Expand Down Expand Up @@ -139,6 +139,38 @@ export const RouteDetailMap = ({
};
}, [routeCoordinates]);

const mapView = (
<MapView
style={styles.map}
styleURL={Mapbox.StyleURL.Street}
logoEnabled={true} // Enterprise
attributionEnabled={true} // Enterprise
scaleBarEnabled={false}
pitchEnabled={false}
scrollEnabled={true}
rotateEnabled={true}
zoomEnabled={true}
{...(Platform.OS === 'android' ? { requestDisallowInterceptTouchEvent: true } : {})}
>
{bounds ? ( // 경로에 따른 카메라 영역이 있을 때
<Camera
bounds={bounds}
animationDuration={0}
minZoomLevel={6}
maxZoomLevel={18}
/>
) : (
// 경로에 따른 카메라 영역이 없을 때
<Camera centerCoordinate={singleCenter!} zoomLevel={12} animationDuration={0} />
)}
{routeFeature && ( // 경로 데이터가 있을 때
<ShapeSource id="route" shape={routeFeature} tolerance={0.3}>
<LineLayer id="routeLine" style={lineLayerStyle} />
</ShapeSource>
)}
</MapView>
);

return (
<View
style={[
Expand All @@ -151,42 +183,16 @@ export const RouteDetailMap = ({
]}
{...props}
>
<NativeViewGestureHandler disallowInterruption={true} shouldActivateOnStart={true}>
<View style={styles.gestureContainer}>
<MapView
style={styles.map}
styleURL={Mapbox.StyleURL.Street}
logoEnabled={true} // Enterprise
attributionEnabled={true} // Enterprise
scaleBarEnabled={false}
pitchEnabled={false}
scrollEnabled={true}
rotateEnabled={true}
zoomEnabled={true}
>
{bounds ? ( // 경로에 따른 카메라 영역이 있을 때
<Camera
bounds={bounds}
animationDuration={0}
minZoomLevel={6}
maxZoomLevel={18}
/>
) : (
// 경로에 따른 카메라 영역이 없을 때
<Camera
centerCoordinate={singleCenter!}
zoomLevel={12}
animationDuration={0}
/>
)}
{routeFeature && ( // 경로 데이터가 있을 때
<ShapeSource id="route" shape={routeFeature} tolerance={0.3}>
<LineLayer id="routeLine" style={lineLayerStyle} />
</ShapeSource>
)}
</MapView>
</View>
</NativeViewGestureHandler>
{Platform.OS === 'ios' ? (
<NativeViewGestureHandler
disallowInterruption={true}
shouldActivateOnStart={true}
>
<View style={styles.gestureContainer}>{mapView}</View>
</NativeViewGestureHandler>
) : (
<View style={styles.gestureContainer}>{mapView}</View>
)}
</View>
);
};
6 changes: 3 additions & 3 deletions src/components/SearchInput/SearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useRef } from 'react';
import { TextInputProps, TextInput, View, Pressable, Text } from 'react-native';
import { useRef, type ComponentRef } from 'react';
import { TextInputProps, View, Pressable, Text } from 'react-native';
import { styles } from './SearchInput.styles';
import SearchInputMarker from '@assets/icons/SearchInputMarker.svg';
import SearchInputDelete from '@assets/icons/SearchInputDelete.svg';
Expand Down Expand Up @@ -28,7 +28,7 @@ export const SearchInput = ({
onCancel,
...rest
}: SearchInputProps) => {
const inputRef = useRef<TextInput>(null);
const inputRef = useRef<ComponentRef<typeof AppTextInput>>(null);

const handleDelete = () => {
onChangeText('');
Expand Down
6 changes: 5 additions & 1 deletion src/screens/HomeScreen/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ export default function HomeScreen() {
router.push('/deviceconnect');
}, [router]);

const handleShokShortcutPress = useCallback(() => {
router.push('/shok');
}, [router]);

const handleRefresh = useCallback(async () => {
setRefreshing(true);
await loadPage(0, false, true);
Expand Down Expand Up @@ -370,7 +374,7 @@ export default function HomeScreen() {
Shok에서 유저들의 운동기록을 확인하고{'\n'}팔로우 할 수 있어요!
</Text>
</View>
<HomeButton label="Shok 바로가기" onPress={() => {}} />
<HomeButton label="Shok 바로가기" onPress={handleShokShortcutPress} />
<HomeButton label="디바이스 연동하기" onPress={handleConnectDevice} />
</View>
)}
Expand Down
8 changes: 7 additions & 1 deletion src/screens/activity_detail/activity_detail.styles.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StyleSheet } from 'react-native';
import { Platform, StyleSheet } from 'react-native';
import { text } from '@src/styles/text';
import { theme } from '@src/theme';

Expand All @@ -7,6 +7,11 @@ export const styles = StyleSheet.create({
flex: 1,
backgroundColor: '#FFFFFF',
},
backButtonScreenLayer: {
...StyleSheet.absoluteFillObject,
zIndex: 100,
...(Platform.OS === 'android' ? { elevation: 24 } : {}),
},
floatingBackButton: {
position: 'absolute',
left: 16,
Expand Down Expand Up @@ -85,6 +90,7 @@ export const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 20,
gap: 10,
},
emptyStateText: {
...text.agS,
Expand Down
14 changes: 8 additions & 6 deletions src/screens/activity_detail/activity_detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -356,12 +356,6 @@ export default function ActivityDetail() {
!hasHeroContent && { paddingTop: contentOffsetWithoutHero },
]}
>
<Header
showBackButton
onBackPress={() => router.back()}
variant="backButtonOnly"
style={[styles.floatingBackButton, { top: backButtonTop }]}
/>
<ActivityHero images={activity.images} map={activity.map} />
<ActivityHeader
userName={activity.author?.nickname ?? '알 수 없는 사용자'}
Expand Down Expand Up @@ -429,6 +423,14 @@ export default function ActivityDetail() {
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
/>
<View style={styles.backButtonScreenLayer} pointerEvents="box-none">
<Header
showBackButton
onBackPress={() => router.back()}
variant="backButtonOnly"
style={[styles.floatingBackButton, { top: backButtonTop }]}
/>
</View>
<CommentBottomSheet
visible={commentSheetVisible}
onClose={() => setCommentSheetVisible(false)}
Expand Down
3 changes: 3 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"jsx": "react-native",
"baseUrl": ".",
"paths": {
"react-native": ["./shims/react-native.tsx"],
"react-native/index": ["./node_modules/react-native/types/index.d.ts"],
"react-native/*": ["./node_modules/react-native/*"],
"@/*": ["./*"],
"@assets/*": ["./assets/*"],
"@src/*": ["./src/*"]
Expand Down
Loading