From 3b4b248fce63d1b5e079405d08051927b6b419eb Mon Sep 17 00:00:00 2001 From: Jiwon Chae <63784453+jiwon0226@users.noreply.github.com> Date: Tue, 13 May 2025 10:48:32 +0900 Subject: [PATCH 1/3] =?UTF-8?q?KW-157/feat:=20QrCard=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.config.js | 2 +- components/cards/QrCard.js | 46 ++++++++++++++++ components/cards/QrCards.js | 0 components/cards/styles/QrCard.styles.js | 65 +++++++++++++++++++++++ components/cards/styles/QrCards.styles.js | 0 pages/MainPage.js | 33 +----------- 6 files changed, 114 insertions(+), 32 deletions(-) create mode 100644 components/cards/QrCard.js create mode 100644 components/cards/QrCards.js create mode 100644 components/cards/styles/QrCard.styles.js create mode 100644 components/cards/styles/QrCards.styles.js diff --git a/app.config.js b/app.config.js index 37b8242..cfb5823 100644 --- a/app.config.js +++ b/app.config.js @@ -25,7 +25,7 @@ export default { favicon: './assets/images/logoIcon.png', }, extra: { - BASE_URL: 'http://192.168.0.115:8081', // 본인 pc IPv4 주소로 수정하세용 + BASE_URL: 'http://192.168.0.225:8081', // 본인 pc IPv4 주소로 수정하세용 }, }, }; diff --git a/components/cards/QrCard.js b/components/cards/QrCard.js new file mode 100644 index 0000000..bea7dd1 --- /dev/null +++ b/components/cards/QrCard.js @@ -0,0 +1,46 @@ +import React, { useState } from 'react'; +import { View, Text, Image } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; +import QRCode from 'react-native-qrcode-svg'; + +import NormalButton from '../../components/buttons/NormalButton'; +import { styles } from './styles/QrCard.styles'; +import { colors } from '../../constants/colors'; + +// hasAccessAuthority: 출입 권한 여부, userVC : VC에 담을 사용자 정보, qrData : QR에 담을 JSON 문자열 +const QrCard = ({ hasAccessAuthority, userVC, qrData }) => { + // 해당 QR의 상세 페이지로 이동 (아직 미구현) + //const navigation = useNavigation(); + // const navigateToAccessListDeatail = () => { + // navigation.navigate('AccessListDetailPage'); + // }; + + return ( + + + + {hasAccessAuthority ? ( + <> + 임시 출입 QR + + {userVC.userName} + {userVC.hospital1} + {userVC.hospital2} + {userVC.hospital3} + + ) : ( + <> + 등록된 출입 권한이 존재하지 않습니다. + 방문 신청 버튼을 눌러 출입 권한을 신청해주세요. + + )} + + + ); +}; + +export default QrCard; diff --git a/components/cards/QrCards.js b/components/cards/QrCards.js new file mode 100644 index 0000000..e69de29 diff --git a/components/cards/styles/QrCard.styles.js b/components/cards/styles/QrCard.styles.js new file mode 100644 index 0000000..87200cb --- /dev/null +++ b/components/cards/styles/QrCard.styles.js @@ -0,0 +1,65 @@ +import { StyleSheet } from 'react-native'; +import { colors } from '../../../constants/colors'; + +export const styles = StyleSheet.create({ + shadowWrapper: { + // 그림자 설정 + shadowColor: 'black', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 5, + elevation: 7, // Android용 + borderRadius: 15, + width: '100%', + height: '55%', + marginVertical: '10%', + }, + cardContainer: { + flex: 1, // 부모(shadowWrapper)의 크기를 꽉 채워 카드와 그림자 모양을 일치시킴 + backgroundColor: colors.white, + borderRadius: 15, + padding: '10%', + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden', // 배경 이미지가 카드 안에 잘리게 + position: 'relative', // 자식의 absolute 포지션 기준점 + }, + backgroundImage: { + ...StyleSheet.absoluteFillObject, // 전체 카드 덮게 만듦 + width: '125%', + zIndex: 0, // 맨 뒤로 + opacity: 0.5, + }, + cardText: { + fontSize: 20, + fontWeight: 600, + color: colors.black, + textAlign: 'center', + marginBottom: '5%', + }, + cardSubText: { + fontSize: 16, + fontWeight: 500, + color: colors.darkGray, + textAlign: 'center', + }, + qrTitle: { + fontSize: 26, + fontWeight: 700, + color: colors.black, + marginBottom: '10%', + zIndex: 1, // QR과 텍스트를 배경 위에 + }, + userName: { + fontSize: 24, + fontWeight: 600, + color: colors.black, + marginVertical: '8%', + }, + hospital: { + fontSize: 16, + fontWeight: 500, + color: colors.darkGray, + marginTop: '2%', + }, +}); diff --git a/components/cards/styles/QrCards.styles.js b/components/cards/styles/QrCards.styles.js new file mode 100644 index 0000000..e69de29 diff --git a/pages/MainPage.js b/pages/MainPage.js index bcc2fbb..85d66de 100644 --- a/pages/MainPage.js +++ b/pages/MainPage.js @@ -5,6 +5,7 @@ import NormalButton from '../components/buttons/NormalButton'; import { styles } from './styles/MainPage.styles'; import QRCode from 'react-native-qrcode-svg'; import { colors } from '../constants/colors'; +import QrCard from '../components/cards/QrCard'; const MainPage = () => { // 임시: 상태변수로 출입 권한 제어 @@ -39,37 +40,7 @@ const MainPage = () => { source={require('../assets/images/logoGreen.png')} resizeMode="contain" // 이미지 비율 유지 /> - - - - {hasAccessAuthority ? ( - <> - 임시 출입 QR - - {userVC.userName} - {userVC.hospital1} - {userVC.hospital2} - {userVC.hospital3} - - ) : ( - <> - 등록된 출입 권한이 존재하지 않습니다. - - 방문 신청 버튼을 눌러 출입 권한을 신청해주세요. - - - )} - - + From 464be253d37626d91c4a0d70f81d9dc1fa8ca9d2 Mon Sep 17 00:00:00 2001 From: Jiwon Chae <63784453+jiwon0226@users.noreply.github.com> Date: Tue, 13 May 2025 13:24:59 +0900 Subject: [PATCH 2/3] =?UTF-8?q?KW-157/feat=20:=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/cards/QrCard.js | 11 +++-- components/cards/QrCards.js | 41 ++++++++++++++++ components/cards/styles/QrCard.styles.js | 4 +- pages/MainPage.js | 34 ++++++++----- pages/styles/MainPage.styles.js | 62 +----------------------- 5 files changed, 73 insertions(+), 79 deletions(-) diff --git a/components/cards/QrCard.js b/components/cards/QrCard.js index bea7dd1..3ee8a62 100644 --- a/components/cards/QrCard.js +++ b/components/cards/QrCard.js @@ -6,15 +6,18 @@ import QRCode from 'react-native-qrcode-svg'; import NormalButton from '../../components/buttons/NormalButton'; import { styles } from './styles/QrCard.styles'; import { colors } from '../../constants/colors'; +import { hospitalName } from '../../mocks/hospitalData'; // hasAccessAuthority: 출입 권한 여부, userVC : VC에 담을 사용자 정보, qrData : QR에 담을 JSON 문자열 -const QrCard = ({ hasAccessAuthority, userVC, qrData }) => { +const QrCard = ({ hasAccessAuthority, did, userName, hospitalName }) => { // 해당 QR의 상세 페이지로 이동 (아직 미구현) //const navigation = useNavigation(); // const navigateToAccessListDeatail = () => { // navigation.navigate('AccessListDetailPage'); // }; + // 임시: QR에 담을 JSON 문자열 + const qrData = JSON.stringify({ did, userName, hospitalName }); return ( @@ -27,10 +30,8 @@ const QrCard = ({ hasAccessAuthority, userVC, qrData }) => { <> 임시 출입 QR - {userVC.userName} - {userVC.hospital1} - {userVC.hospital2} - {userVC.hospital3} + {userName} + {hospitalName} ) : ( <> diff --git a/components/cards/QrCards.js b/components/cards/QrCards.js index e69de29..4038671 100644 --- a/components/cards/QrCards.js +++ b/components/cards/QrCards.js @@ -0,0 +1,41 @@ +import React from 'react'; +import { FlatList, View, Dimensions } from 'react-native'; +import QrCard from './QrCard'; + +const { width } = Dimensions.get('window'); + +const QrCards = ({ userVC, hasAccessAuthority }) => { + // 권한 없거나 카드 데이터 없으면 안내 메시지 카드만 + if (!hasAccessAuthority || !userVC || userVC.length === 0) { + return ( + + + + ); + } + + // 카드 리스트 + return ( + + item.did} + horizontal //가로 스크롤 + pagingEnabled //한 페이지씩 스크롤 + showsHorizontalScrollIndicator={false} //하단 기본 스크롤바 숨김 + renderItem={({ item }) => ( + + + + )} + /> + + ); +}; + +export default QrCards; diff --git a/components/cards/styles/QrCard.styles.js b/components/cards/styles/QrCard.styles.js index 87200cb..45c6505 100644 --- a/components/cards/styles/QrCard.styles.js +++ b/components/cards/styles/QrCard.styles.js @@ -10,8 +10,8 @@ export const styles = StyleSheet.create({ shadowRadius: 5, elevation: 7, // Android용 borderRadius: 15, - width: '100%', - height: '55%', + width: '80%', + height: '80%', marginVertical: '10%', }, cardContainer: { diff --git a/pages/MainPage.js b/pages/MainPage.js index 85d66de..967dbe3 100644 --- a/pages/MainPage.js +++ b/pages/MainPage.js @@ -5,23 +5,35 @@ import NormalButton from '../components/buttons/NormalButton'; import { styles } from './styles/MainPage.styles'; import QRCode from 'react-native-qrcode-svg'; import { colors } from '../constants/colors'; -import QrCard from '../components/cards/QrCard'; +import QrCards from '../components/cards/QrCards'; const MainPage = () => { // 임시: 상태변수로 출입 권한 제어 const [hasAccessAuthority, setHasAccessAuthority] = useState(true); // 임시: VC에 담을 사용자 정보 - const userVC = { - did: 'did:example:123456789abcdefghi', - userName: '김짱구', - hospital1: '짱구병원', - hospital2: '흰둥이병원', - hospital3: '오수병원', - issuedAt: Date.now(), - }; + const userVC = [ + { + did: 'did:example:123456789abcdefghi', + userName: '김짱구', + hospitalName: '짱구병원', + issuedAt: Date.now(), + }, + { + did: 'did:example:123456789abcdefdhi', + userName: '김짱구', + hospitalName: '흰둥이병원', + issuedAt: Date.now(), + }, + { + did: 'did:example:123456789abcdeffhi', + userName: '김짱구', + hospitalName: '오수병원', + issuedAt: Date.now(), + }, + ]; // 임시: QR에 담을 JSON 문자열 - const qrData = JSON.stringify(userVC); + //const qrData = JSON.stringify(userVC); const navigation = useNavigation(); @@ -40,7 +52,7 @@ const MainPage = () => { source={require('../assets/images/logoGreen.png')} resizeMode="contain" // 이미지 비율 유지 /> - + diff --git a/pages/styles/MainPage.styles.js b/pages/styles/MainPage.styles.js index 9321614..60e6bc4 100644 --- a/pages/styles/MainPage.styles.js +++ b/pages/styles/MainPage.styles.js @@ -4,7 +4,7 @@ import { colors } from '../../constants/colors'; export const styles = StyleSheet.create({ container: { flex: 1, - paddingHorizontal: '10%', + // paddingHorizontal: '10%', paddingTop: '5%', }, logoImage: { @@ -13,66 +13,6 @@ export const styles = StyleSheet.create({ marginTop: '15%', alignSelf: 'center', }, - shadowWrapper: { - // 그림자 설정 - shadowColor: 'black', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.2, - shadowRadius: 5, - elevation: 7, // Android용 - borderRadius: 15, - width: '100%', - height: '55%', - marginVertical: '10%', - }, - cardContainer: { - flex: 1, // 부모(shadowWrapper)의 크기를 꽉 채워 카드와 그림자 모양을 일치시킴 - backgroundColor: colors.white, - borderRadius: 15, - padding: '10%', - alignItems: 'center', - justifyContent: 'center', - overflow: 'hidden', // 배경 이미지가 카드 안에 잘리게 - position: 'relative', // 자식의 absolute 포지션 기준점 - }, - backgroundImage: { - ...StyleSheet.absoluteFillObject, // 전체 카드 덮게 만듦 - width: '125%', - zIndex: 0, // 맨 뒤로 - opacity: 0.5, - }, - cardText: { - fontSize: 20, - fontWeight: 600, - color: colors.black, - textAlign: 'center', - marginBottom: '5%', - }, - cardSubText: { - fontSize: 16, - fontWeight: 500, - color: colors.darkGray, - textAlign: 'center', - }, - qrTitle: { - fontSize: 26, - fontWeight: 700, - color: colors.black, - marginBottom: '10%', - zIndex: 1, // QR과 텍스트를 배경 위에 - }, - userName: { - fontSize: 24, - fontWeight: 600, - color: colors.black, - marginVertical: '8%', - }, - hospital: { - fontSize: 16, - fontWeight: 500, - color: colors.darkGray, - marginTop: '2%', - }, buttonContainer: { flexDirection: 'row', justifyContent: 'center', From 412b3b667e0b38c5ab3c46550d9397c82ba508b2 Mon Sep 17 00:00:00 2001 From: Jiwon Chae <63784453+jiwon0226@users.noreply.github.com> Date: Tue, 13 May 2025 15:09:16 +0900 Subject: [PATCH 3/3] =?UTF-8?q?KW-157/feat:=20=EC=B9=B4=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20dot=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.config.js | 2 +- components/cards/Dot.js | 36 ++++++++++++++++ components/cards/QrCards.js | 52 ++++++++++++++++++++--- components/cards/styles/Dot.styles.js | 10 +++++ components/cards/styles/QrCard.styles.js | 2 +- components/cards/styles/QrCards.styles.js | 11 +++++ pages/styles/MainPage.styles.js | 3 +- 7 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 components/cards/Dot.js create mode 100644 components/cards/styles/Dot.styles.js diff --git a/app.config.js b/app.config.js index cfb5823..374b754 100644 --- a/app.config.js +++ b/app.config.js @@ -25,7 +25,7 @@ export default { favicon: './assets/images/logoIcon.png', }, extra: { - BASE_URL: 'http://192.168.0.225:8081', // 본인 pc IPv4 주소로 수정하세용 + BASE_URL: 'http://192.168.0.181:8081', // 본인 pc IPv4 주소로 수정하세용 }, }, }; diff --git a/components/cards/Dot.js b/components/cards/Dot.js new file mode 100644 index 0000000..ede8791 --- /dev/null +++ b/components/cards/Dot.js @@ -0,0 +1,36 @@ +import React, { useEffect, useRef } from 'react'; +import { Animated, StyleSheet } from 'react-native'; +import { styles } from './styles/Dot.styles'; +import { colors } from '../../constants/colors'; + +// dot의 기본 너비와 활성화(선택) 상태일 때의 너비 +const DOT_WIDTH = 10; +const DOT_ACTIVE_WIDTH = 16; + +const Dot = ({ active }) => { + // dot width를 위한 Animated.Value 배열 + const animatedWidth = useRef(new Animated.Value(active ? DOT_ACTIVE_WIDTH : DOT_WIDTH)).current; + + // pageIndex가 바뀔 때 dot width 애니메이션 + useEffect(() => { + Animated.timing(animatedWidth, { + toValue: active ? DOT_ACTIVE_WIDTH : DOT_WIDTH, + duration: 300, + useNativeDriver: false, + }).start(); + }, [active, animatedWidth]); + + return ( + + ); +}; + +export default Dot; diff --git a/components/cards/QrCards.js b/components/cards/QrCards.js index 4038671..5dfc9b1 100644 --- a/components/cards/QrCards.js +++ b/components/cards/QrCards.js @@ -1,10 +1,19 @@ -import React from 'react'; -import { FlatList, View, Dimensions } from 'react-native'; +import React, { useRef, useState } from 'react'; +import { FlatList, View, Dimensions, TouchableOpacity } from 'react-native'; import QrCard from './QrCard'; +import styles from './styles/QrCards.styles'; +import Dot from './Dot'; +// 화면의 가로 길이 가져오기 const { width } = Dimensions.get('window'); +const CARD_WIDTH = width; +const SIDE_PADDING = (width - CARD_WIDTH) / 2; // 좌우 패딩 계산 +const OVERLAP = 80; // 카드가 겹치는 정도 const QrCards = ({ userVC, hasAccessAuthority }) => { + const [pageIndex, setPageIndex] = useState(0); + const flatListRef = useRef(null); + // 권한 없거나 카드 데이터 없으면 안내 메시지 카드만 if (!hasAccessAuthority || !userVC || userVC.length === 0) { return ( @@ -14,17 +23,42 @@ const QrCards = ({ userVC, hasAccessAuthority }) => { ); } + // 스크롤이 끝났을 때 현재 페이지 인덱스 계산 + const onMomentumScrollEnd = (event) => { + const offsetX = event.nativeEvent.contentOffset.x; + const newIndex = Math.round(offsetX / (CARD_WIDTH - OVERLAP)); + setPageIndex(newIndex); + }; + + // dot(인디케이터) 클릭 시 해당 카드로 이동 + const handleDotPress = (index) => { + flatListRef.current?.scrollToIndex({ index, animated: true }); + }; + // 카드 리스트 return ( item.did} horizontal //가로 스크롤 - pagingEnabled //한 페이지씩 스크롤 showsHorizontalScrollIndicator={false} //하단 기본 스크롤바 숨김 - renderItem={({ item }) => ( - + pagingEnabled={false} // snapToInterval을 사용하므로 false + snapToInterval={CARD_WIDTH - OVERLAP} // 카드 단위로 스냅 + decelerationRate="fast" // 빠른 스냅 효과 + contentContainerStyle={{ + paddingHorizontal: SIDE_PADDING, // 양쪽에 패딩 추가로 옆 카드 살짝 보이게 + }} + renderItem={({ item, index }) => ( + { /> )} + onMomentumScrollEnd={onMomentumScrollEnd} /> + + {userVC.map((_, i) => ( + handleDotPress(i)}> + + + ))} + ); }; diff --git a/components/cards/styles/Dot.styles.js b/components/cards/styles/Dot.styles.js new file mode 100644 index 0000000..5d09691 --- /dev/null +++ b/components/cards/styles/Dot.styles.js @@ -0,0 +1,10 @@ +import { StyleSheet } from 'react-native'; +import { colors } from '../../../constants/colors'; + +export const styles = StyleSheet.create({ + dot: { + height: 10, + borderRadius: 16, + marginHorizontal: 4, + }, +}); diff --git a/components/cards/styles/QrCard.styles.js b/components/cards/styles/QrCard.styles.js index 45c6505..7f13ae1 100644 --- a/components/cards/styles/QrCard.styles.js +++ b/components/cards/styles/QrCard.styles.js @@ -10,7 +10,7 @@ export const styles = StyleSheet.create({ shadowRadius: 5, elevation: 7, // Android용 borderRadius: 15, - width: '80%', + width: '70%', height: '80%', marginVertical: '10%', }, diff --git a/components/cards/styles/QrCards.styles.js b/components/cards/styles/QrCards.styles.js index e69de29..0502f92 100644 --- a/components/cards/styles/QrCards.styles.js +++ b/components/cards/styles/QrCards.styles.js @@ -0,0 +1,11 @@ +import { StyleSheet } from 'react-native'; + +const styles = StyleSheet.create({ + dotContainer: { + flexDirection: 'row', + justifyContent: 'center', + marginBottom: '5%', + }, +}); + +export default styles; diff --git a/pages/styles/MainPage.styles.js b/pages/styles/MainPage.styles.js index 60e6bc4..091dddd 100644 --- a/pages/styles/MainPage.styles.js +++ b/pages/styles/MainPage.styles.js @@ -4,7 +4,6 @@ import { colors } from '../../constants/colors'; export const styles = StyleSheet.create({ container: { flex: 1, - // paddingHorizontal: '10%', paddingTop: '5%', }, logoImage: { @@ -17,6 +16,6 @@ export const styles = StyleSheet.create({ flexDirection: 'row', justifyContent: 'center', marginTop: '3%', - gap: '10%', + gap: '5%', }, });