diff --git a/app/(post)/feed/write.tsx b/app/(post)/feed/write.tsx index 8a02d93f..1973da59 100644 --- a/app/(post)/feed/write.tsx +++ b/app/(post)/feed/write.tsx @@ -92,11 +92,13 @@ export default function FeedWritePage() { const hasAnyImage = mediaUrls.length > 0; - const previewMedia: Media[] = mediaUrls.map((m, idx) => ({ - position: idx + 1, - mediaUrl: m.mediaUrl, - mediaType: m.mediaType, - })); + const previewMedia: Media[] = mediaUrls + .filter((m) => m.mediaUrl && m.mediaUrl.trim() !== '') + .map((m, idx) => ({ + position: idx + 1, + mediaUrl: m.mediaUrl, + mediaType: m.mediaType, + })); const requestBodyCreate = { description, @@ -160,8 +162,6 @@ export default function FeedWritePage() { setIsModalOpen(false); setIsUploading(true); try { - // if (__DEV__) console.log('수정 내용 : ', requestBodyEdit); - const idNum = typeof feedId === 'string' ? Number(feedId) : NaN; if (Number.isNaN(idNum) || idNum <= 0) { showToast('잘못된 피드 아이디야!', 'error'); @@ -214,10 +214,23 @@ export default function FeedWritePage() { onScrollBeginDrag={Keyboard.dismiss} > - + {previewMedia.length > 0 && ( + + )} + + {kbVisible && ( + + )} {/* 헤더 */}
{ + router.back(); + }, 900); + + return; } } finally { if (!signal?.canceled) setLoading(false); @@ -192,7 +200,7 @@ export default function LeenkDetailPage() { await deleteLeenk(leenkDetail.id); showToast('삭제 완료!', 'success'); closeModal(); - router.replace('/leenk'); + router.replace('/(page)/leenk'); } catch (err) { console.error('링크 삭제 오류:', err); showToast('삭제에 실패했어. 잠시 후 다시 시도해 줘.', 'error'); @@ -273,14 +281,20 @@ export default function LeenkDetailPage() { }); // 예: "leenk://leenk/123" - // iOS는 url 필드를 더 잘 인식 - if (Platform.OS === 'ios') { - await Share.share({ url: deepLink, message: leenkDetail?.title }); - } else { - await Share.share({ message: `${leenkDetail?.title}\n${deepLink}` }); + const shareOptions = { + title: leenkDetail?.title, + message: `${leenkDetail?.title}\n${deepLink}`, + url: deepLink, + }; + + await Share.open(shareOptions); + } catch (error: any) { + // 사용자가 공유를 취소한 경우 (error.message === 'User did not share') + if (error?.message && error.message.includes('User did not share')) { + return; // 에러 표시 없이 종료 } - } catch { - // 실패 시 클립보드 복사 폴백 + + // 실제 에러인 경우 클립보드 복사 폴백 const fallback = Linking.createURL(`/leenk/${leenkId}`, { scheme: 'leenk', }); diff --git a/app/account/setting/notifications.tsx b/app/account/setting/notifications.tsx index 87f93b00..5bf7b317 100644 --- a/app/account/setting/notifications.tsx +++ b/app/account/setting/notifications.tsx @@ -8,6 +8,7 @@ import { getNotificationsSetting, patchNotificationsSetting, } from '@/api/users/notification.api'; +import * as Haptics from 'expo-haptics'; export default function SettingNotificationsPage() { const [toggles, setToggles] = useState({ @@ -57,6 +58,9 @@ export default function SettingNotificationsPage() { const handleToggle = async (key: keyof typeof toggles, apiKey: string) => { const newValue = !toggles[key]; + //햅틱 + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Soft); + setToggles((prev) => ({ ...prev, [key]: newValue, @@ -66,6 +70,8 @@ export default function SettingNotificationsPage() { await patchNotificationsSetting({ [apiKey]: newValue }); } catch (error) { console.error('알림 설정 업데이트 실패:', error); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); + setToggles((prev) => ({ ...prev, [key]: !newValue, diff --git a/components/Modal/NotificationModal.tsx b/components/Modal/NotificationModal.tsx index f508324e..2ac246d9 100644 --- a/components/Modal/NotificationModal.tsx +++ b/components/Modal/NotificationModal.tsx @@ -1,7 +1,6 @@ -import React from 'react'; import { Modal, Platform, TouchableWithoutFeedback } from 'react-native'; import styled from 'styled-components/native'; -import { width, height, radius } from '@/theme/globalStyles'; +import { width, height, radius, fonts } from '@/theme/globalStyles'; import colors from '@/theme/color'; import { FlatList } from 'react-native-gesture-handler'; import { @@ -38,7 +37,7 @@ export default function NotificationModal({ ): item is NewLeenkParticipantDetails => (item as NewLeenkParticipantDetails).participantName !== undefined; - const isScrollable = data.length > 5; + const isScrollable = data.length >= 4; return ( - {item.body} - {item.name} + {item.body} + {item.name} ); @@ -105,7 +104,7 @@ export default function NotificationModal({ - {item.participantName} + {item.participantName} ); @@ -171,6 +170,9 @@ const Item = styled.View` const ContentContainer = styled.View` margin-left: ${28 * width}px; + display: flex; + gap: ${6 * height}px; + align-items: flex-start; `; const GradientOverlay = styled(LinearGradient).attrs({ @@ -183,3 +185,7 @@ const GradientOverlay = styled(LinearGradient).attrs({ height: ${60 * height}px; z-index: 1; `; + +const StyledTitle = styled(SubText)` + color: ${colors.text[1]}; +`; diff --git a/components/Modal/UserListModal.tsx b/components/Modal/UserListModal.tsx index dff35690..885df383 100644 --- a/components/Modal/UserListModal.tsx +++ b/components/Modal/UserListModal.tsx @@ -1,10 +1,17 @@ -import { Modal, Pressable, Platform, KeyboardAvoidingView } from 'react-native'; +import { + Modal, + Pressable, + Platform, + KeyboardAvoidingView, + Animated, +} from 'react-native'; import styled from 'styled-components/native'; import { BlurView } from 'expo-blur'; import colors from '@/theme/color'; import { fonts, fontSize, radius, height, width } from '@/theme/globalStyles'; import { FeedReactedUser, FeedConnectedUser } from '@/types/feed'; import UserListModalContent from '../feed/UserListModalContent'; +import { useEffect, useRef } from 'react'; interface Props { visible: boolean; @@ -19,14 +26,61 @@ export default function UserListModal({ list, onClose, }: Props) { + const fadeAnim = useRef(new Animated.Value(0)).current; + const slideAnim = useRef(new Animated.Value(500)).current; + + useEffect(() => { + if (visible) { + Animated.parallel([ + Animated.timing(fadeAnim, { + toValue: 1, + duration: 120, + useNativeDriver: true, + }), + Animated.spring(slideAnim, { + toValue: 0, + damping: 20, + stiffness: 220, + mass: 0.6, + useNativeDriver: true, + }), + ]).start(); + } else { + Animated.parallel([ + Animated.timing(fadeAnim, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }), + Animated.timing(slideAnim, { + toValue: 500, + duration: 350, + useNativeDriver: true, + }), + ]).start(); + } + }, [visible, fadeAnim, slideAnim]); + return ( - - + + + - + @@ -42,15 +96,19 @@ export default function UserListModal({ - - + + ); } -const Backdrop = styled.Pressable` - flex: 1; - justify-content: flex-end; +const AnimatedBackdrop = styled(Animated.View)` + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); `; const SheetContainer = styled.View` diff --git a/components/common/Header/KebabButton.tsx b/components/common/Header/KebabButton.tsx index 972748c6..c66233c4 100644 --- a/components/common/Header/KebabButton.tsx +++ b/components/common/Header/KebabButton.tsx @@ -14,7 +14,10 @@ export default function KebabButton({ const iconColor = color === 'white' ? colors.white : colors.black; return ( - {})}> + {})} + hitSlop={{ top: 16, bottom: 16, left: 16, right: 16 }} + > ); diff --git a/components/common/ImagePicker.tsx b/components/common/ImagePicker.tsx index 7fef9d75..de0182b9 100644 --- a/components/common/ImagePicker.tsx +++ b/components/common/ImagePicker.tsx @@ -101,6 +101,11 @@ export default function ImagePicker({ windowSize={5} removeClippedSubviews ListFooterComponent={pagingLoading ? : null} + ListFooterComponentStyle={{ + marginTop: 44 * height, + marginBottom: 40 * height, + alignItems: 'center', + }} renderItem={({ item }) => ( { + if (isFirstRender.current) { + // 최초 진입 시 애니메이션 없이 바로 반영 + anim.setValue(isOn ? 1 : 0); + isFirstRender.current = false; + return; + } + + Animated.timing(anim, { + toValue: isOn ? 1 : 0, + duration: 180, + useNativeDriver: false, + }).start(); + }, [isOn, anim]); + + const translateX = anim.interpolate({ + inputRange: [0, 1], + outputRange: [0, 15 * width], // Thumb 이동 거리 + }); + + const trackColor = anim.interpolate({ + inputRange: [0, 1], + outputRange: [colors.gray[400], colors.primary], + }); + return ( - - - + + + ); } @@ -24,19 +57,17 @@ const ToggleWrapper = styled(Pressable)` justify-content: center; `; -const Track = styled.View<{ isOn: boolean }>` +const AnimatedTrack = styled(Animated.View)` width: 100%; height: 100%; border-radius: ${100 * height}px; - background-color: ${({ isOn }) => (isOn ? colors.primary : colors.gray[400])}; - padding-horizontal: ${4 * width}px; + padding: 0 ${2.5 * width}px; justify-content: center; `; -const Thumb = styled.View<{ isOn: boolean }>` +const AnimatedThumb = styled(Animated.View)` width: ${16 * height}px; height: ${16 * height}px; border-radius: ${100 * height}px; background-color: ${colors.white}; - align-self: ${({ isOn }) => (isOn ? 'flex-end' : 'flex-start')}; `; diff --git a/components/feed/FeedDetailItem.tsx b/components/feed/FeedDetailItem.tsx index 91ff1dcf..3141698b 100644 --- a/components/feed/FeedDetailItem.tsx +++ b/components/feed/FeedDetailItem.tsx @@ -41,6 +41,8 @@ export default function FeedDetailItem({ feed }: Props) { const { modalType, openModal, closeModal, payload } = useModalStore(); const isOpen = modalType === 'feedLinked' && payload?.feedId === feed.feedId; const isMenuOpen = modalType === 'menu' && payload?.feedId === feed.feedId; + const isDeleteConfirmOpen = + modalType === 'deleteConfirm' && payload?.feedId === feed.feedId; const { showToast } = useToastStore(); const { userInfo } = useUserStore(); @@ -107,8 +109,8 @@ export default function FeedDetailItem({ feed }: Props) { // 피드 삭제/신고 로직 const handleDelete = useCallback(() => { closeModal(); - openModal('deleteConfirm'); - }, [closeModal, openModal]); + openModal('deleteConfirm', null, { feedId: feed.feedId }); + }, [closeModal, openModal, feed.feedId]); const handleReport = useCallback(() => { closeModal(); @@ -237,7 +239,7 @@ export default function FeedDetailItem({ feed }: Props) { onPressSecond={isAuthor ? handleDelete : undefined} /> - {modalType === 'deleteConfirm' && ( + {isDeleteConfirmOpen && ( {data.title} + {/* + + */} @@ -67,9 +70,7 @@ export default function LeenkContentSection({ - {data.author.name} - {''}・{''} - {formatRelativeTime(data.createdAt)} + {data.author.name} ・ {formatRelativeTime(data.createdAt)} diff --git a/components/mypage/MypageButton.tsx b/components/mypage/MypageButton.tsx index 0d169858..bea44ddc 100644 --- a/components/mypage/MypageButton.tsx +++ b/components/mypage/MypageButton.tsx @@ -1,7 +1,6 @@ import { RightArrowIcon } from '@/assets'; import colors from '@/theme/color'; import { fonts, fontSize, width } from '@/theme/globalStyles'; -import React from 'react'; import styled from 'styled-components/native'; import Toggle from '@/components/common/Toggle'; diff --git a/hooks/useFeedDetailNavigation.ts b/hooks/useFeedDetailNavigation.ts index 71f9be3f..da3d075e 100644 --- a/hooks/useFeedDetailNavigation.ts +++ b/hooks/useFeedDetailNavigation.ts @@ -3,6 +3,7 @@ import { FlatList, ViewToken } from 'react-native'; import { router } from 'expo-router'; import { getFeedNavigation } from '@/api/feed/feed.api'; import { FeedNavigationItem } from '@/types/feed'; +import { useToastStore } from '@/stores/toastStore'; const transformFeedItem = (item: FeedNavigationItem, uniqueKey?: string) => ({ feedId: item.feedId, @@ -24,6 +25,7 @@ export function useFeedDetailNavigation(initialFeedId: number) { const [hasMorePrev, setHasMorePrev] = useState(false); const [hasMoreNext, setHasMoreNext] = useState(false); const [isLoadingMore, setIsLoadingMore] = useState(false); + const { showToast } = useToastStore(); const flatListRef = useRef(null); const currentIndexRef = useRef(0); @@ -65,7 +67,17 @@ export function useFeedDetailNavigation(initialFeedId: number) { currentIndexRef.current = navigation.prevFeeds.length; initialLoadDoneRef.current = true; - } catch (e) { + } catch (e: any) { + const status = e?.response?.status; + + if (status === 404) { + showToast('삭제된 피드야!', 'error'); + + setTimeout(() => { + router.back(); + }, 900); + return; + } console.error('피드 네비게이션 초기 로드 실패', e); } finally { setIsLoading(false); diff --git a/ios/Leenk.xcodeproj/project.pbxproj b/ios/Leenk.xcodeproj/project.pbxproj index 1fb5e950..5ceb606b 100644 --- a/ios/Leenk.xcodeproj/project.pbxproj +++ b/ios/Leenk.xcodeproj/project.pbxproj @@ -173,8 +173,8 @@ LastUpgradeCheck = 1130; TargetAttributes = { 13B07F861A680F5B00A75B9A = { + DevelopmentTeam = 2VCK7Z2C2W; LastSwiftMigration = 1250; - DevelopmentTeam = "2VCK7Z2C2W"; ProvisioningStyle = Automatic; }; }; @@ -257,8 +257,6 @@ "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", ); name = "[CP-User] [RNFB] Crashlytics Configuration"; - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\nif [[ ${PODS_ROOT} ]]; then\n echo \"info: Exec FirebaseCrashlytics Run from Pods\"\n \"${PODS_ROOT}/FirebaseCrashlytics/run\"\nelse\n echo \"info: Exec FirebaseCrashlytics Run from framework\"\n \"${PROJECT_DIR}/FirebaseCrashlytics.framework/run\"\nfi\n"; @@ -272,8 +270,6 @@ "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", ); name = "[CP-User] [RNFB] Core Configuration"; - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"note: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"note: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"note: -> RNFB build script started\"\necho \"note: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"note: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"note: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n if ! _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\"); then\n echo \"error: Failed to parse firebase.json, check for syntax errors.\"\n exit 1\n fi\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"error: python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"note: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"note: <- RNFB build script finished\"\n"; @@ -429,7 +425,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = "2VCK7Z2C2W"; + DEVELOPMENT_TEAM = 2VCK7Z2C2W; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 5DPG335P5J; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -472,7 +468,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = "2VCK7Z2C2W"; + DEVELOPMENT_TEAM = 2VCK7Z2C2W; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 5DPG335P5J; INFOPLIST_FILE = Leenk/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; @@ -564,7 +560,10 @@ LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -630,7 +629,10 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = NO; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 05f4c7b9..7bebc7c9 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2630,6 +2630,30 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - RNShare (12.2.1): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - RNSVG (15.12.0): - DoubleConversion - glog @@ -2815,6 +2839,7 @@ DEPENDENCIES: - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNReanimated (from `../node_modules/react-native-reanimated`) - RNScreens (from `../node_modules/react-native-screens`) + - RNShare (from `../node_modules/react-native-share`) - RNSVG (from `../node_modules/react-native-svg`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) @@ -3092,6 +3117,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-reanimated" RNScreens: :path: "../node_modules/react-native-screens" + RNShare: + :path: "../node_modules/react-native-share" RNSVG: :path: "../node_modules/react-native-svg" Yoga: @@ -3243,6 +3270,7 @@ SPEC CHECKSUMS: RNGestureHandler: 8ff2b1434b0ff8bab28c8242a656fb842990bbc8 RNReanimated: a3f55346df73f35c38bf0b446294d3d0b1ac8cf9 RNScreens: 90b905d545a5ebbe976985702b8a39e3475727b2 + RNShare: 1012edc984c2c4123041012fcb94e9e0a1df46d5 RNSVG: 45e3c3210465e75ab6374c9f746179e75d76ce48 SDWebImage: d0184764be51240d49c761c37f53dd017e1ccaaf SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57 diff --git a/package-lock.json b/package-lock.json index 8a820ed4..be439d2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,6 +69,7 @@ "react-native-reanimated-carousel": "^4.0.2", "react-native-safe-area-context": "5.4.0", "react-native-screens": "~4.11.1", + "react-native-share": "^12.2.1", "react-native-svg": "^15.12.0", "react-native-svg-transformer": "^1.5.1", "react-native-web": "~0.20.0", @@ -13901,6 +13902,15 @@ "react-native": "*" } }, + "node_modules/react-native-share": { + "version": "12.2.1", + "resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-12.2.1.tgz", + "integrity": "sha512-DPfvqQMbbLK4ykPkqYarby5AXdgFsbefOhsQHkOrDeAIixWzeI4oe/WvI7AoYmlQxM4Ys2DcBPBWDYQ6gn5xYA==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/react-native-svg": { "version": "15.12.0", "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.12.0.tgz", diff --git a/package.json b/package.json index e4f1b047..ff3bbf90 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "react-native-reanimated-carousel": "^4.0.2", "react-native-safe-area-context": "5.4.0", "react-native-screens": "~4.11.1", + "react-native-share": "^12.2.1", "react-native-svg": "^15.12.0", "react-native-svg-transformer": "^1.5.1", "react-native-web": "~0.20.0",