From d4e5d7a8686f516cc9ec2ec87268413606ec7e42 Mon Sep 17 00:00:00 2001 From: Hee Su Date: Sat, 6 Apr 2024 02:32:43 +0900 Subject: [PATCH] =?UTF-8?q?Feature:=20=EC=95=88=EB=93=9C=EB=A1=9C=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=20=EB=92=A4=EB=A1=9C=EA=B0=80=EA=B8=B0=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EA=B0=9C=EC=84=A0,=20Footer=20=EA=B0=9C=EC=84=A0,?= =?UTF-8?q?=20splash=20screen=20=EA=B0=9C=EC=84=A0=20(#679)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: footer icon을 교체 방식에서 동적으로 색상을 바꾸도록 변경 * refactor: android 뒤로가기 버튼 개선, body 높이 크로스브라우징 해결 * refactor: Onboard splash screen노출 시간 개선 * refactor: staltime 다시 추가 --- packages/app/src/components/MainNavigator.tsx | 2 - packages/app/src/components/OnBoarding.tsx | 22 +++-- packages/app/src/components/TabBar.tsx | 2 +- .../app/src/components/WebViewContainer.tsx | 81 +++++++++---------- .../icons/32-footer-community_default.svg | 2 +- packages/web/public/sprite/sprite.svg | 28 ++++--- packages/web/src/apis/community/queries.ts | 1 + packages/web/src/apis/groups/queries.ts | 1 + .../components/ArticleDetailHeader.tsx | 2 +- .../src/app/[lng]/(main)/community/page.tsx | 10 +-- packages/web/src/app/[lng]/layout.tsx | 12 ++- packages/web/src/components/Footer/Footer.tsx | 2 +- packages/web/src/components/Icon/Icon.tsx | 4 +- .../components/Image/ImageWithPlaceholder.tsx | 24 ------ .../src/components/Provider/QueryProvider.tsx | 1 + packages/web/src/utils/cn.ts | 3 +- 16 files changed, 89 insertions(+), 108 deletions(-) delete mode 100644 packages/web/src/components/Image/ImageWithPlaceholder.tsx diff --git a/packages/app/src/components/MainNavigator.tsx b/packages/app/src/components/MainNavigator.tsx index 68c510076..d55d8a4cc 100644 --- a/packages/app/src/components/MainNavigator.tsx +++ b/packages/app/src/components/MainNavigator.tsx @@ -3,7 +3,6 @@ import { TransitionPresets, createStackNavigator } from '@react-navigation/stack import React from 'react'; import OnBoarding from './OnBoarding'; -import TabBarNavigator from './TabBarNavigator'; import WebViewContainer from './WebViewContainer'; import { useDidMount } from '@/hooks/useDidMount'; @@ -29,7 +28,6 @@ export default function MainNavigator() { }} > - diff --git a/packages/app/src/components/OnBoarding.tsx b/packages/app/src/components/OnBoarding.tsx index 6de15dd2f..c05d6fb53 100644 --- a/packages/app/src/components/OnBoarding.tsx +++ b/packages/app/src/components/OnBoarding.tsx @@ -1,5 +1,6 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import { useNavigation } from '@react-navigation/native'; +import { StackNavigationProp } from '@react-navigation/stack'; import { useEffect, useRef, useState } from 'react'; import { Animated, Dimensions, Image, Text, TouchableOpacity, View } from 'react-native'; import { ExpandingDot } from 'react-native-animated-pagination-dots'; @@ -27,16 +28,21 @@ const imageDataList = [ ]; export default function OnBoarding() { - const navigation = useNavigation(); + const navigation = useNavigation>(); + const [isPreloadingDone, setIsPreloadingDone] = useState(false); const scrollX = useRef(new Animated.Value(0)).current; const [pageIndex, setpageIndex] = useState(0); - const [lang, setlang] = useState(null); + const [lang, setlang] = useState(null); + const preloading = async () => { const isUserOnBoardSeen = await AsyncStorage.getItem('onBoarding'); if (!isUserOnBoardSeen) return; + navigation.replace('WebViewContainer', { url: `${SOURCE_URL}`, }); + + setIsPreloadingDone(true); }; useEffect(() => { @@ -44,11 +50,14 @@ export default function OnBoarding() { const languageCode = locales && locales.length > 0 ? locales[0].languageCode : 'en'; setlang(languageCode); preloading(); - setTimeout(() => { - SplashScreen.hide(); - }, 500); }, []); + useEffect(() => { + if (isPreloadingDone) { + SplashScreen.hide(); + } + }, [isPreloadingDone]); + return ( {lang === 'ko' ? : } onboard-image1 {lang === 'ko' ? : } onboard-image2 ) : ( onboard-image3 value in ScreenIconName; const getScreenIcon = (value: string) => isKeyScreenIconName(value) ? ScreenIconName[value as ScreenIconNameKey] : undefined; -function TabBar({ state, navigation, descriptors }: BottomTabBarProps) { +function TabBar({ state, navigation }: BottomTabBarProps) { const onPress = useTabBarPress(); const renderTabBarButton = useCallback( (name: string, index: number) => ( diff --git a/packages/app/src/components/WebViewContainer.tsx b/packages/app/src/components/WebViewContainer.tsx index d9773c9a3..4d04b2a4c 100644 --- a/packages/app/src/components/WebViewContainer.tsx +++ b/packages/app/src/components/WebViewContainer.tsx @@ -1,32 +1,34 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; import messaging from '@react-native-firebase/messaging'; -import { StackActions, useNavigation, useRoute } from '@react-navigation/native'; +import { RouteProp, StackActions, useNavigation, useRoute } from '@react-navigation/native'; import React, { useEffect, useRef, useState } from 'react'; import { Alert, BackHandler, Dimensions, Linking, Platform } from 'react-native'; -import RNRestart from 'react-native-restart'; // Import package from node modules +import RNRestart from 'react-native-restart'; import { SafeAreaView } from 'react-native-safe-area-context'; -import WebView from 'react-native-webview'; +import WebView, { WebViewMessageEvent, WebViewNavigation } from 'react-native-webview'; import Error from './Error'; import { SOURCE_URL } from '../config'; import { sendFCMTokenToWebView } from '../utils/sendFCMTokenToWebView'; import { useDidMount } from '@/hooks/useDidMount'; -import useWebViewNavigationSetUp from '@/hooks/useWebViewNavigationSetUp'; import { getPermission } from '@/utils/getPermission'; -import sendMessageToWebview from '@/utils/sendMessageToWebview'; const windowWidth = Dimensions.get('window').width; const windowHeight = Dimensions.get('window').height; +type WebViewParams = { + url?: string; + edges?: any[]; + title?: string; +}; + export default function WebViewContainer() { - useWebViewNavigationSetUp(); const navigation = useNavigation(); - const params = useRoute().params; - const webViewRef = useRef(null); + const params = useRoute>().params; + const webViewRef = useRef(null); const { isError, setIsError, onWebViewError } = useAppError(); + const [canGoBackInWebView, setCanGoBackInWebView] = useState(false); const url = params.url ?? SOURCE_URL; - const edges = params?.edges; useDidMount(async () => { /* 권한 요청 */ @@ -34,11 +36,20 @@ export default function WebViewContainer() { await messaging().requestPermission(); }); - /* 안드로이드 뒤로가기 */ - useDidMount(() => { + // Android 뒤로가기 버튼 이벤트 처리 + useEffect(() => { + if (Platform.OS !== 'android') return; + const handleAndroidBackPress = () => { - if (navigation.canGoBack()) navigation.goBack(); - else { + console.log('back pushed ' + canGoBackInWebView); + + if (canGoBackInWebView && webViewRef.current) { + webViewRef.current.goBack(); + return true; + } else if (navigation.canGoBack()) { + navigation.goBack(); + return true; + } else { Alert.alert( 'Hold on!(잠시만요!)', 'Are you sure you want to quit the program?(앱을 종료하시겠습니까?)', @@ -50,33 +61,27 @@ export default function WebViewContainer() { { text: 'Confirm(확인)', onPress: () => BackHandler.exitApp() }, ] ); - } - - if (webViewRef.current) { - webViewRef.current.goBack(); return true; } - return false; }; - if (Platform.OS === 'android') { - BackHandler.addEventListener('hardwareBackPress', handleAndroidBackPress); - return () => { - BackHandler.removeEventListener('hardwareBackPress', handleAndroidBackPress); - }; - } - }); + BackHandler.addEventListener('hardwareBackPress', handleAndroidBackPress); + return () => { + BackHandler.removeEventListener('hardwareBackPress', handleAndroidBackPress); + }; - /* (iOS)외부 페이지 이동 */ - const onNavigationStateChange = (navState) => { - if (!navState.url.includes(SOURCE_URL)) { - Linking.openURL(navState.url).catch((err) => {}); - return false; - } + /** + * addEventListener로 등록된 handleAndroidBackPress가 생성시의 클로저 환경을 유지하기 때문에 + * canGoBackInWebView의 상태를 추적하기 위해 의존성 배열에 canGoBackInWebView와 navigation추가 + */ + }, [canGoBackInWebView, navigation]); + + const onNavigationStateChange = (navState: WebViewNavigation) => { + setCanGoBackInWebView(navState.canGoBack); }; - const onShouldStartLoadWithRequest = (navState) => { - if (!navState.url.includes(SOURCE_URL)) { + const onShouldStartLoadWithRequest = (navState: WebViewNavigation) => { + if (SOURCE_URL && !navState.url.includes(SOURCE_URL)) { Linking.openURL(navState.url).catch((err) => {}); return false; } @@ -84,7 +89,7 @@ export default function WebViewContainer() { }; /* 페이지 이동 */ - const requestOnMessage = async (event) => { + const requestOnMessage = async (event: WebViewMessageEvent) => { const nativeEvent = JSON.parse(event.nativeEvent.data); const { type, data } = nativeEvent; switch (type) { @@ -96,11 +101,6 @@ export default function WebViewContainer() { url: `${SOURCE_URL}${path}`, title, edges: title ? ['bottom'] : [], - // right: () => { // 어디서 쓰는지 잘 모르겠는데 WARNING 떠서 주석처리 - // webViewRef.current?.postMessage( - // JSON.stringify({type: 'NAVIGATION_TAPPED_RIGHT_BUTTON'}), - // ); - // }, }); navigation.dispatch(pushAction); break; @@ -164,7 +164,6 @@ export default function WebViewContainer() { pullToRefreshEnabled thirdPartyCookiesEnabled={true} sharedCookiesEnabled={true} - androidHardwareAccelerationDisabled onNavigationStateChange={onNavigationStateChange} onShouldStartLoadWithRequest={onShouldStartLoadWithRequest} onMessage={requestOnMessage} // 웹뷰 -> 앱으로 통신 diff --git a/packages/web/public/sprite/icons/32-footer-community_default.svg b/packages/web/public/sprite/icons/32-footer-community_default.svg index 928c68a31..7cfa1d631 100644 --- a/packages/web/public/sprite/icons/32-footer-community_default.svg +++ b/packages/web/public/sprite/icons/32-footer-community_default.svg @@ -1,3 +1,3 @@ - + diff --git a/packages/web/public/sprite/sprite.svg b/packages/web/public/sprite/sprite.svg index 9d449aa76..17a45d898 100644 --- a/packages/web/public/sprite/sprite.svg +++ b/packages/web/public/sprite/sprite.svg @@ -501,38 +501,40 @@ - - - - - + + + + + + + - + - + - + - + - + - + - + - + diff --git a/packages/web/src/apis/community/queries.ts b/packages/web/src/apis/community/queries.ts index f0694df32..6cbca5665 100644 --- a/packages/web/src/apis/community/queries.ts +++ b/packages/web/src/apis/community/queries.ts @@ -17,6 +17,7 @@ export const useGetCommunityArticles = (categoryId: number) => { ? lastPage.data.currentPage + 1 : undefined, initialPageParam: 0, + staleTime: 0, }); return { diff --git a/packages/web/src/apis/groups/queries.ts b/packages/web/src/apis/groups/queries.ts index 0d9075a17..c2071be95 100644 --- a/packages/web/src/apis/groups/queries.ts +++ b/packages/web/src/apis/groups/queries.ts @@ -20,6 +20,7 @@ export const useGetGroups = () => { getNextPageParam: (lastPage) => lastPage.totalPage !== lastPage.currentPage ? lastPage.currentPage + 1 : undefined, initialPageParam: 0, + staleTime: 0, }); return { diff --git a/packages/web/src/app/[lng]/(main)/community/detail/[articleId]/components/ArticleDetailHeader.tsx b/packages/web/src/app/[lng]/(main)/community/detail/[articleId]/components/ArticleDetailHeader.tsx index 49d0ccd6c..da33febc5 100644 --- a/packages/web/src/app/[lng]/(main)/community/detail/[articleId]/components/ArticleDetailHeader.tsx +++ b/packages/web/src/app/[lng]/(main)/community/detail/[articleId]/components/ArticleDetailHeader.tsx @@ -46,8 +46,8 @@ function IconButtonAction() { const { articleId } = useNumberParams<['articleId']>(); const { setBlockId } = useBlockStore(); const { back } = useAppRouter(); - const { data: articleData } = useGetCommunityArticleDetail(articleId); const { open: openModal, exit: closeModal } = useModal(); + const { data: articleData } = useGetCommunityArticleDetail(articleId); const { mutate: mutateDelete } = usePostDeleteCommunityArticle(articleId); const handleBlockArticle = () => { diff --git a/packages/web/src/app/[lng]/(main)/community/page.tsx b/packages/web/src/app/[lng]/(main)/community/page.tsx index 54a4c316e..8f06995da 100644 --- a/packages/web/src/app/[lng]/(main)/community/page.tsx +++ b/packages/web/src/app/[lng]/(main)/community/page.tsx @@ -4,9 +4,7 @@ import CommunityHeader from './components/CommunityHeader'; import ContentSection from './components/ContentSection'; import CreateArticleButton from './components/CreateArticleButton'; -import { Keys, getCommunityArticles } from '@/apis/community'; import { ErrorFallback } from '@/components/ErrorBoundary'; -import { HydrationProvider } from '@/components/Provider'; import { Spacing } from '@/components/Spacing'; interface CommunityPageProps { @@ -20,13 +18,7 @@ export default function CommunityPage({ params: { lng } }: CommunityPageProps) { <> - getCommunityArticles({ categoryId: 0, pageParam: 0 })} - queryKey={Keys.getCommunityArticles(0)} - isInfiniteQuery - > - - + diff --git a/packages/web/src/app/[lng]/layout.tsx b/packages/web/src/app/[lng]/layout.tsx index 8908c34ea..5ca69de6f 100644 --- a/packages/web/src/app/[lng]/layout.tsx +++ b/packages/web/src/app/[lng]/layout.tsx @@ -73,14 +73,12 @@ export default function RootLayout({ - - - - - {children} - + {children} + + + ); } @@ -92,7 +90,7 @@ interface LayoutProps { function Layout({ lng, children }: StrictPropsWithChildren) { return ( - +
{children}
diff --git a/packages/web/src/components/Footer/Footer.tsx b/packages/web/src/components/Footer/Footer.tsx index cb2ceb7d4..442305cc8 100644 --- a/packages/web/src/components/Footer/Footer.tsx +++ b/packages/web/src/components/Footer/Footer.tsx @@ -67,7 +67,7 @@ export default function Footer({ isSpacing = true, spacingColor }: FooterProps) > { id: string; className?: string; diff --git a/packages/web/src/components/Image/ImageWithPlaceholder.tsx b/packages/web/src/components/Image/ImageWithPlaceholder.tsx deleted file mode 100644 index 5835fdfaf..000000000 --- a/packages/web/src/components/Image/ImageWithPlaceholder.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import Image from 'next/image'; - -import getBase64 from '@/utils/getBase64FromBuffer'; - -interface ImageWithPlaceholderProps { - src: string; -} - -export default async function ImageWithPlaceholder({ src }: ImageWithPlaceholderProps) { - const { base64, img } = await getBase64(src); - - return ( - {src} - ); -} diff --git a/packages/web/src/components/Provider/QueryProvider.tsx b/packages/web/src/components/Provider/QueryProvider.tsx index 8d5935c35..0ffcacb3c 100644 --- a/packages/web/src/components/Provider/QueryProvider.tsx +++ b/packages/web/src/components/Provider/QueryProvider.tsx @@ -18,6 +18,7 @@ export default function QueryProvider({ children }: StrictPropsWithChildren) { queries: { retry: 0, throwOnError: true, + staleTime: 60 * 1000, }, mutations: { onError: (error) => { diff --git a/packages/web/src/utils/cn.ts b/packages/web/src/utils/cn.ts index 3716d282c..dd36ba074 100644 --- a/packages/web/src/utils/cn.ts +++ b/packages/web/src/utils/cn.ts @@ -1,9 +1,10 @@ -import { fontSize } from '@/style/theme'; import { clsx } from 'clsx'; import { extendTailwindMerge } from 'tailwind-merge'; import type { ClassValue } from 'clsx'; +import { fontSize } from '@/style/theme'; + const customTwMerge = extendTailwindMerge({ classGroups: { 'font-size': [