victor.zhou пре 4 месеци
родитељ
комит
a3f585329c

+ 122 - 0
ios/Podfile.lock

@@ -1,13 +1,42 @@
 PODS:
+  - AppAuth (2.0.0):
+    - AppAuth/Core (= 2.0.0)
+    - AppAuth/ExternalUserAgent (= 2.0.0)
+  - AppAuth/Core (2.0.0)
+  - AppAuth/ExternalUserAgent (2.0.0):
+    - AppAuth/Core
+  - AppCheckCore (11.2.0):
+    - GoogleUtilities/Environment (~> 8.0)
+    - GoogleUtilities/UserDefaults (~> 8.0)
+    - PromisesObjC (~> 2.4)
   - boost (1.84.0)
   - DoubleConversion (1.1.6)
   - fast_float (8.0.0)
   - FBLazyVector (0.80.2)
   - fmt (11.0.2)
   - glog (0.3.5)
+  - GoogleSignIn (9.0.0):
+    - AppAuth (~> 2.0)
+    - AppCheckCore (~> 11.0)
+    - GTMAppAuth (~> 5.0)
+    - GTMSessionFetcher/Core (~> 3.3)
+  - GoogleUtilities/Environment (8.1.0):
+    - GoogleUtilities/Privacy
+  - GoogleUtilities/Logger (8.1.0):
+    - GoogleUtilities/Environment
+    - GoogleUtilities/Privacy
+  - GoogleUtilities/Privacy (8.1.0)
+  - GoogleUtilities/UserDefaults (8.1.0):
+    - GoogleUtilities/Logger
+    - GoogleUtilities/Privacy
+  - GTMAppAuth (5.0.0):
+    - AppAuth/Core (~> 2.0)
+    - GTMSessionFetcher/Core (< 4.0, >= 3.3)
+  - GTMSessionFetcher/Core (3.5.0)
   - hermes-engine (0.80.2):
     - hermes-engine/Pre-built (= 0.80.2)
   - hermes-engine/Pre-built (0.80.2)
+  - PromisesObjC (2.4.0)
   - RCT-Folly (2024.11.18.00):
     - boost
     - DoubleConversion
@@ -1767,6 +1796,35 @@ PODS:
     - ReactCommon/turbomodule/core
     - SocketRocket
     - Yoga
+  - react-native-webview (13.16.0):
+    - boost
+    - DoubleConversion
+    - fast_float
+    - fmt
+    - glog
+    - hermes-engine
+    - RCT-Folly
+    - RCT-Folly/Fabric
+    - 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
+    - SocketRocket
+    - Yoga
   - React-NativeModulesApple (0.80.2):
     - boost
     - DoubleConversion
@@ -2241,6 +2299,8 @@ PODS:
     - React-perflogger (= 0.80.2)
     - React-utils (= 0.80.2)
     - SocketRocket
+  - RNAppleAuthentication (2.4.1):
+    - React-Core
   - RNCMaskedView (0.3.2):
     - boost
     - DoubleConversion
@@ -2270,6 +2330,8 @@ PODS:
     - ReactCommon/turbomodule/core
     - SocketRocket
     - Yoga
+  - RNDeviceInfo (14.1.1):
+    - React-Core
   - RNGestureHandler (2.29.0):
     - boost
     - DoubleConversion
@@ -2299,6 +2361,36 @@ PODS:
     - ReactCommon/turbomodule/core
     - SocketRocket
     - Yoga
+  - RNGoogleSignin (16.0.0):
+    - boost
+    - DoubleConversion
+    - fast_float
+    - fmt
+    - glog
+    - GoogleSignIn (~> 9.0)
+    - hermes-engine
+    - RCT-Folly
+    - RCT-Folly/Fabric
+    - 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
+    - SocketRocket
+    - Yoga
   - RNReanimated (4.1.3):
     - boost
     - DoubleConversion
@@ -2647,6 +2739,7 @@ DEPENDENCIES:
   - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
   - react-native-mmkv (from `../node_modules/react-native-mmkv`)
   - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
+  - react-native-webview (from `../node_modules/react-native-webview`)
   - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
   - React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`)
   - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
@@ -2678,8 +2771,11 @@ DEPENDENCIES:
   - ReactAppDependencyProvider (from `build/generated/ios`)
   - ReactCodegen (from `build/generated/ios`)
   - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
+  - "RNAppleAuthentication (from `../node_modules/@invertase/react-native-apple-authentication`)"
   - "RNCMaskedView (from `../node_modules/@react-native-masked-view/masked-view`)"
+  - RNDeviceInfo (from `../node_modules/react-native-device-info`)
   - RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
+  - "RNGoogleSignin (from `../node_modules/@react-native-google-signin/google-signin`)"
   - RNReanimated (from `../node_modules/react-native-reanimated`)
   - RNScreens (from `../node_modules/react-native-screens`)
   - RNSVG (from `../node_modules/react-native-svg`)
@@ -2689,6 +2785,13 @@ DEPENDENCIES:
 
 SPEC REPOS:
   trunk:
+    - AppAuth
+    - AppCheckCore
+    - GoogleSignIn
+    - GoogleUtilities
+    - GTMAppAuth
+    - GTMSessionFetcher
+    - PromisesObjC
     - SocketRocket
 
 EXTERNAL SOURCES:
@@ -2777,6 +2880,8 @@ EXTERNAL SOURCES:
     :path: "../node_modules/react-native-mmkv"
   react-native-safe-area-context:
     :path: "../node_modules/react-native-safe-area-context"
+  react-native-webview:
+    :path: "../node_modules/react-native-webview"
   React-NativeModulesApple:
     :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios"
   React-oscompat:
@@ -2839,10 +2944,16 @@ EXTERNAL SOURCES:
     :path: build/generated/ios
   ReactCommon:
     :path: "../node_modules/react-native/ReactCommon"
+  RNAppleAuthentication:
+    :path: "../node_modules/@invertase/react-native-apple-authentication"
   RNCMaskedView:
     :path: "../node_modules/@react-native-masked-view/masked-view"
+  RNDeviceInfo:
+    :path: "../node_modules/react-native-device-info"
   RNGestureHandler:
     :path: "../node_modules/react-native-gesture-handler"
+  RNGoogleSignin:
+    :path: "../node_modules/@react-native-google-signin/google-signin"
   RNReanimated:
     :path: "../node_modules/react-native-reanimated"
   RNScreens:
@@ -2855,13 +2966,20 @@ EXTERNAL SOURCES:
     :path: "../node_modules/react-native/ReactCommon/yoga"
 
 SPEC CHECKSUMS:
+  AppAuth: 1c1a8afa7e12f2ec3a294d9882dfa5ab7d3cb063
+  AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
   boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
   DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
   fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6
   FBLazyVector: 86588b5a1547e7a417942a08f49559b184e002c8
   fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
   glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
+  GoogleSignIn: c7f09cfbc85a1abf69187be091997c317cc33b77
+  GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
+  GTMAppAuth: 217a876b249c3c585a54fd6f73e6b58c4f5c4238
+  GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
   hermes-engine: bbc1152da7d2d40f9e59c28acc6576fcf5d28e2a
+  PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
   RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
   RCTDeprecation: 300c5eb91114d4339b0bb39505d0f4824d7299b7
   RCTRequired: e0446b01093475b7082fbeee5d1ef4ad1fe20ac4
@@ -2897,6 +3015,7 @@ SPEC CHECKSUMS:
   React-microtasksnativemodule: 8ff9cb220a8efa625b5885996bd69e69db9edf02
   react-native-mmkv: a079b4492bf9cb13e3fa6535556cb72085d13e89
   react-native-safe-area-context: b6714cf77746147d1fef3f89892e4f82e35a4ef6
+  react-native-webview: a5d11a99159d88fb9f7ab45ad57b549e232218d5
   React-NativeModulesApple: 37c08c3c54db55854de816b0df0f3683832be35a
   React-oscompat: 56d6de59f9ae95cd006a1c40be2cde83bc06a4e1
   React-perflogger: 4008bd05a8b6c157b06608c0ea0b8bd5d9c5e6c9
@@ -2928,8 +3047,11 @@ SPEC CHECKSUMS:
   ReactAppDependencyProvider: 8df342c127fd0c1e30e8b9f71ff814c22414a7c0
   ReactCodegen: 439c427ccc115d71d16cc84256e5fbdc7fcef57a
   ReactCommon: 592ef441605638b95e533653259254b4bd35ff4f
+  RNAppleAuthentication: c3dddf5918126c9aae85dc2e2ce9fb87835e9e04
   RNCMaskedView: a8ae6be2c3f50e525e9354b21d62aa55317eca1a
+  RNDeviceInfo: 0682ce0060b1e2ca0b6618d934fb51466d175a1a
   RNGestureHandler: b10adefe65287088d425204632d519c800a935e5
+  RNGoogleSignin: 258e9877f77405711597d77c1dc6647c29390c68
   RNReanimated: 955f1dab987d22aeca21e6f2c1a45919c51b0b6f
   RNScreens: c63849403489bd068ea160f276fbc8416f19f2f7
   RNSVG: cb3156ab4865f6a8bc145fb431a1003f02df2ff7

+ 13 - 4
ios/PrivacyInfo.xcprivacy

@@ -6,18 +6,19 @@
 	<array>
 		<dict>
 			<key>NSPrivacyAccessedAPIType</key>
-			<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
+			<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
 			<key>NSPrivacyAccessedAPITypeReasons</key>
 			<array>
-				<string>C617.1</string>
+				<string>CA92.1</string>
+				<string>C56D.1</string>
 			</array>
 		</dict>
 		<dict>
 			<key>NSPrivacyAccessedAPIType</key>
-			<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
+			<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
 			<key>NSPrivacyAccessedAPITypeReasons</key>
 			<array>
-				<string>CA92.1</string>
+				<string>C617.1</string>
 			</array>
 		</dict>
 		<dict>
@@ -28,6 +29,14 @@
 				<string>35F9.1</string>
 			</array>
 		</dict>
+		<dict>
+			<key>NSPrivacyAccessedAPIType</key>
+			<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
+			<key>NSPrivacyAccessedAPITypeReasons</key>
+			<array>
+				<string>85F4.1</string>
+			</array>
+		</dict>
 	</array>
 	<key>NSPrivacyCollectedDataTypes</key>
 	<array/>

+ 19 - 7
ios/recipemuse.xcodeproj/project.pbxproj

@@ -34,6 +34,7 @@
 		41F925D6E74036F703711B79 /* libPods-recipemuse.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-recipemuse.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = recipemuse/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
+		F0590DE12EB8AFB200070097 /* recipemuse.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = recipemuse.entitlements; path = recipemuse/recipemuse.entitlements; sourceTree = "<group>"; };
 		F571B3872BD7A3F80014213C /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
 		F5DC7AB02D9D8A92004DB744 /* AppDelegade.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegade.swift; sourceTree = "<group>"; };
 		F5DC7AB22D9D8AA1004DB744 /* recipemuse-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "recipemuse-Bridging-Header.h"; sourceTree = "<group>"; };
@@ -61,6 +62,7 @@
 		13B07FAE1A68108700A75B9A /* recipemuse */ = {
 			isa = PBXGroup;
 			children = (
+				F0590DE12EB8AFB200070097 /* recipemuse.entitlements */,
 				F5DC7AB02D9D8A92004DB744 /* AppDelegade.swift */,
 				F571B3872BD7A3F80014213C /* PrivacyInfo.xcprivacy */,
 				13B07FB51A68108700A75B9A /* Images.xcassets */,
@@ -243,10 +245,14 @@
 			inputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-recipemuse/Pods-recipemuse-resources-${CONFIGURATION}-input-files.xcfilelist",
 			);
+			inputPaths = (
+			);
 			name = "[CP] Copy Pods Resources";
 			outputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-recipemuse/Pods-recipemuse-resources-${CONFIGURATION}-output-files.xcfilelist",
 			);
+			outputPaths = (
+			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-recipemuse/Pods-recipemuse-resources.sh\"\n";
@@ -260,10 +266,14 @@
 			inputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-recipemuse/Pods-recipemuse-frameworks-${CONFIGURATION}-input-files.xcfilelist",
 			);
+			inputPaths = (
+			);
 			name = "[CP] Embed Pods Frameworks";
 			outputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-recipemuse/Pods-recipemuse-frameworks-${CONFIGURATION}-output-files.xcfilelist",
 			);
+			outputPaths = (
+			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-recipemuse/Pods-recipemuse-frameworks.sh\"\n";
@@ -359,7 +369,7 @@
 					"-lc++",
 					"$(inherited)",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
+				PRODUCT_BUNDLE_IDENTIFIER = com.ipeaking.recipemuse;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/recipemuse.app/recipemuse";
 			};
@@ -382,7 +392,7 @@
 					"-lc++",
 					"$(inherited)",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
+				PRODUCT_BUNDLE_IDENTIFIER = com.ipeaking.recipemuse;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/recipemuse.app/recipemuse";
 			};
@@ -394,8 +404,9 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = recipemuse/recipemuse.entitlements;
 				CURRENT_PROJECT_VERSION = 1;
-				DEVELOPMENT_TEAM = HH673SN3QR;
+				DEVELOPMENT_TEAM = 5T7S9KSHU2;
 				ENABLE_BITCODE = NO;
 				INFOPLIST_FILE = recipemuse/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = (
@@ -408,8 +419,8 @@
 					"-ObjC",
 					"-lc++",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
-				"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = tcm.app.recipemuse;
+				PRODUCT_BUNDLE_IDENTIFIER = com.ipeaking.recipemuse;
+				"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.ipeaking.recipemuse;
 				PRODUCT_NAME = recipemuse;
 				SWIFT_OBJC_BRIDGING_HEADER = "recipemuse-Bridging-Header.h";
 				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -424,8 +435,9 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = recipemuse/recipemuse.entitlements;
 				CURRENT_PROJECT_VERSION = 1;
-				DEVELOPMENT_TEAM = HH673SN3QR;
+				DEVELOPMENT_TEAM = 5T7S9KSHU2;
 				INFOPLIST_FILE = recipemuse/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
@@ -437,7 +449,7 @@
 					"-ObjC",
 					"-lc++",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
+				PRODUCT_BUNDLE_IDENTIFIER = com.ipeaking.recipemuse;
 				PRODUCT_NAME = recipemuse;
 				SWIFT_OBJC_BRIDGING_HEADER = "recipemuse-Bridging-Header.h";
 				SWIFT_VERSION = 5.0;

+ 12 - 0
ios/recipemuse/recipemuse.entitlements

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>com.apple.developer.applesignin</key>
+	<array>
+		<string>Default</string>
+	</array>
+	<key>com.apple.developer.in-app-payments</key>
+	<array/>
+</dict>
+</plist>

+ 24 - 0
package-lock.json

@@ -1482,6 +1482,11 @@
       "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
       "dev": true
     },
+    "@invertase/react-native-apple-authentication": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmmirror.com/@invertase/react-native-apple-authentication/-/react-native-apple-authentication-2.4.1.tgz",
+      "integrity": "sha512-SzWXS/T6H62ScTX+jMLJFLAcBp2p8P30J5uQEimpLMUolqnv/iAljfpwTvZYaniVmMSUXjG0/N95ScUIcmsYRQ=="
+    },
     "@isaacs/ttlcache": {
       "version": "1.4.1",
       "resolved": "https://registry.npmmirror.com/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz",
@@ -2285,6 +2290,11 @@
         "joi": "^17.2.1"
       }
     },
+    "@react-native-google-signin/google-signin": {
+      "version": "16.0.0",
+      "resolved": "https://registry.npmmirror.com/@react-native-google-signin/google-signin/-/google-signin-16.0.0.tgz",
+      "integrity": "sha512-jVuzPo8odREekFc0b4RK3YsqCvedtLIM2P6NSszFr9cYyhKrUNikffPapL6LmkL9qkb8K6pDeb5CXg4qALOc0g=="
+    },
     "@react-native-masked-view/masked-view": {
       "version": "0.3.2",
       "resolved": "https://registry.npmmirror.com/@react-native-masked-view/masked-view/-/masked-view-0.3.2.tgz",
@@ -8942,6 +8952,11 @@
         "yargs": "^17.6.2"
       }
     },
+    "react-native-device-info": {
+      "version": "14.1.1",
+      "resolved": "https://registry.npmmirror.com/react-native-device-info/-/react-native-device-info-14.1.1.tgz",
+      "integrity": "sha512-lXFpe6DJmzbQXNLWxlMHP2xuTU5gwrKAvI8dCAZuERhW9eOXSubOQIesk9lIBnsi9pI19GMrcpJEvs4ARPRYmw=="
+    },
     "react-native-gesture-handler": {
       "version": "2.29.0",
       "resolved": "https://registry.npmmirror.com/react-native-gesture-handler/-/react-native-gesture-handler-2.29.0.tgz",
@@ -9015,6 +9030,15 @@
         "path-dirname": "^1.0.2"
       }
     },
+    "react-native-webview": {
+      "version": "13.16.0",
+      "resolved": "https://registry.npmmirror.com/react-native-webview/-/react-native-webview-13.16.0.tgz",
+      "integrity": "sha512-Nh13xKZWW35C0dbOskD7OX01nQQavOzHbCw9XoZmar4eXCo7AvrYJ0jlUfRVVIJzqINxHlpECYLdmAdFsl9xDA==",
+      "requires": {
+        "escape-string-regexp": "^4.0.0",
+        "invariant": "2.2.4"
+      }
+    },
     "react-native-worklets": {
       "version": "0.5.2",
       "resolved": "https://registry.npmmirror.com/react-native-worklets/-/react-native-worklets-0.5.2.tgz",

+ 4 - 0
package.json

@@ -16,6 +16,8 @@
     "pod-install": "npx pod-install"
   },
   "dependencies": {
+    "@invertase/react-native-apple-authentication": "^2.4.1",
+    "@react-native-google-signin/google-signin": "^16.0.0",
     "@react-native-masked-view/masked-view": "^0.3.2",
     "@react-navigation/native": "^7.1.16",
     "@react-navigation/stack": "^7.4.4",
@@ -27,12 +29,14 @@
     "react-error-boundary": "^5.0.0",
     "react-i18next": "^15.6.1",
     "react-native": "0.80.2",
+    "react-native-device-info": "^14.1.1",
     "react-native-gesture-handler": "^2.27.2",
     "react-native-mmkv": "^3.3.0",
     "react-native-reanimated": "^4.1.3",
     "react-native-safe-area-context": "^5.5.2",
     "react-native-screens": "4.13.1",
     "react-native-svg": "^15.12.1",
+    "react-native-webview": "^13.16.0",
     "react-native-worklets": "^0.5.2",
     "zod": "^4.0.14"
   },

+ 80 - 2
src/App.tsx

@@ -1,8 +1,11 @@
 import 'react-native-gesture-handler';
 
+import React, { createContext, useContext, useRef, useState } from 'react';
+import { Platform, StyleSheet, View } from 'react-native';
 import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
 import { GestureHandlerRootView } from 'react-native-gesture-handler';
 import { MMKV } from 'react-native-mmkv';
+import { WebView } from 'react-native-webview';
 
 import ApplicationNavigator from '@/navigation/Application';
 import { ThemeProvider } from '@/theme';
@@ -21,16 +24,91 @@ export const queryClient = new QueryClient({
 
 export const storage = new MMKV();
 
+// WebView预加载上下文
+interface WebViewContextType {
+  webViewRef: React.RefObject<WebView | null>;
+  showWebView: boolean;
+  setShowWebView: (show: boolean) => void;
+}
+
+const WebViewContext = createContext<WebViewContextType | null>(null);
+
+export const useWebViewContext = () => {
+  const context = useContext(WebViewContext);
+  if (!context) {
+    throw new Error('useWebViewContext must be used within WebViewProvider');
+  }
+  return context;
+};
+
+// 配置常量
+const DEFAULT_URL = 'http://localhost:5173';
+
+// WebView用户代理
+const USER_AGENT = Platform.select({
+  ios: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1',
+  android: 'Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36',
+});
+
 function App() {
+  const webViewRef = useRef<WebView>(null);
+  const [showWebView, setShowWebView] = useState(false);
+
   return (
-    <GestureHandlerRootView>
+    <GestureHandlerRootView style={styles.container}>
       <QueryClientProvider client={queryClient}>
         <ThemeProvider storage={storage}>
-          <ApplicationNavigator />
+          <WebViewContext.Provider value={{ webViewRef, showWebView, setShowWebView }}>
+            <View style={styles.container}>
+              <ApplicationNavigator />
+              
+              {/* 预加载的WebView,通过样式控制显示/隐藏 */}
+              <View style={[
+                styles.webViewContainer,
+                showWebView ? styles.webViewVisible : styles.webViewHidden,
+              ]}>
+                <WebView
+                  ref={webViewRef}
+                  source={{ uri: DEFAULT_URL }}
+                  style={styles.webView}
+                  userAgent={USER_AGENT}
+                  javaScriptEnabled={true}
+                  domStorageEnabled={true}
+                  startInLoadingState={true}
+                  scalesPageToFit={true}
+                  allowsBackForwardNavigationGestures={Platform.OS === 'ios'}
+                />
+              </View>
+            </View>
+          </WebViewContext.Provider>
         </ThemeProvider>
       </QueryClientProvider>
     </GestureHandlerRootView>
   );
 }
 
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+  },
+  webViewContainer: {
+    position: 'absolute',
+    top: 0,
+    left: 0,
+    right: 0,
+    bottom: 0,
+    zIndex: 1000,
+  },
+  webViewVisible: {
+    opacity: 1,
+  },
+  webViewHidden: {
+    opacity: 0,
+    transform: [{ translateX: -9999 }], // 偏移到屏幕外
+  },
+  webView: {
+    flex: 1,
+  },
+});
+
 export default App;

+ 29 - 0
src/README.md

@@ -0,0 +1,29 @@
+https://thecodingmachine.github.io/react-native-boilerplate/docs/navigate
+
+
+
+
+
+
+api 文档: https://www.yuque.com/kolo7/ly4p08/koe2e766ehtb7gai?singleDoc# 《PAYMENT_API》
+https://www.yuque.com/kolo7/ly4p08/yps2h45o8l3cktgb?singleDoc# 《AUTH_API》
+设计文档:https://www.yuque.com/kolo7/ly4p08/lgz7bru6f2gvrciq?singleDoc# 《统一登录充值平台》
+https://www.yuque.com/kolo7/ly4p08/uuwa89hedxi4qtds?singleDoc# 《google Pay》
+
+包名:com.ipeaking.recipemuse
+
+
+
+
+Google控制台
+https://console.cloud.google.com/welcome?project=lithe-land-475707-b1&authuser=1
+
+
+
+Apple控制台
+https://developer.apple.com/account
+ 
+
+apple 沙盒测试用户
+mealmuse@gmail.com
+Abc753951

+ 33 - 0
src/config/auth.ts

@@ -0,0 +1,33 @@
+// 认证配置文件
+export const AUTH_CONFIG = {
+  // Google登录配置
+  GOOGLE: {
+    // Web客户端ID - React Native必需,用于身份验证和获取ID Token
+    WEB_CLIENT_ID: 'YOUR_WEB_CLIENT_ID_HERE',
+    // Android客户端ID(可选,用于原生功能)
+    ANDROID_CLIENT_ID: 'YOUR_ANDROID_CLIENT_ID_HERE',
+    // iOS客户端ID(可选,用于原生功能)
+    IOS_CLIENT_ID: 'YOUR_IOS_CLIENT_ID_HERE',
+  },
+  
+  // Apple登录配置
+  APPLE: {
+    // Apple开发者账号中的Service ID
+    SERVICE_ID: 'YOUR_APPLE_SERVICE_ID_HERE',
+  },
+};
+
+// 登录提供商枚举
+export enum LoginProvider {
+  APPLE = 'apple',
+  GOOGLE = 'google',
+}
+
+// 用户信息接口
+export interface UserInfo {
+  id: string;
+  name: string;
+  email: string;
+  avatar?: string;
+  provider: LoginProvider;
+}

+ 2 - 2
src/hooks/language/schema.ts

@@ -2,12 +2,12 @@ import * as z from 'zod';
 
 export const enum SupportedLanguages {
   EN_EN = 'en-EN',
-  FR_FR = 'fr-FR',
+  ZH_CN = 'zh-CN',
 }
 
 export const languageSchema = z.enum([
   SupportedLanguages.EN_EN,
-  SupportedLanguages.FR_FR,
+  SupportedLanguages.ZH_CN,
 ]);
 
 export type Language = z.infer<typeof languageSchema>;

+ 3 - 1
src/navigation/Application.tsx

@@ -7,7 +7,7 @@ import { SafeAreaProvider } from 'react-native-safe-area-context';
 import { Paths } from '@/navigation/paths';
 import { useTheme } from '@/theme';
 
-import { Example, Startup } from '@/screens';
+import { Example, Startup, Login, Home} from '@/screens';
 
 const Stack = createStackNavigator<RootStackParamList>();
 
@@ -20,6 +20,8 @@ function ApplicationNavigator() {
         <Stack.Navigator key={variant} screenOptions={{ headerShown: false }}>
           <Stack.Screen component={Startup} name={Paths.Startup} />
           <Stack.Screen component={Example} name={Paths.Example} />
+          <Stack.Screen component={Login} name={Paths.Login} />
+          <Stack.Screen component={Home} name={Paths.Home} />
         </Stack.Navigator>
       </NavigationContainer>
     </SafeAreaProvider>

+ 2 - 0
src/navigation/paths.ts

@@ -1,4 +1,6 @@
 export const enum Paths {
   Example = 'example',
   Startup = 'startup',
+  Login = 'login',
+  Home = 'home',
 }

+ 2 - 0
src/navigation/types.ts

@@ -8,4 +8,6 @@ export type RootScreenProps<
 export type RootStackParamList = {
   [Paths.Example]: undefined;
   [Paths.Startup]: undefined;
+  [Paths.Login]: undefined;
+  [Paths.Home]: undefined;
 };

+ 1 - 1
src/screens/Example/Example.test.tsx

@@ -43,7 +43,7 @@ describe('Example screen should render correctly', () => {
 
     render(component);
 
-    expect(i18n.language).toBe(SupportedLanguages.FR_FR);
+    expect(i18n.language).toBe(SupportedLanguages.ZH_CN);
 
     const button = screen.getByTestId('change-language-button');
     expect(button).toBeDefined();

+ 2 - 3
src/screens/Example/Example.tsx

@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 import { Alert, ScrollView, Text, TouchableOpacity, View } from 'react-native';
 
-import { useI18n, useUser } from '@/hooks';
+import { useI18n, useFetchOneQuery } from '@/hooks';
 import { useTheme } from '@/theme';
 
 import { AssetByVariant, IconByVariant, Skeleton } from '@/components/atoms';
@@ -12,7 +12,6 @@ const MAX_RANDOM_ID = 9;
 
 function Example() {
   const { t } = useTranslation();
-  const { useFetchOneQuery } = useUser();
   const { toggleLanguage } = useI18n();
 
   const {
@@ -31,7 +30,7 @@ function Example() {
   const fetchOneUserQuery = useFetchOneQuery(currentId);
 
   useEffect(() => {
-    if (fetchOneUserQuery.isSuccess) {
+    if (fetchOneUserQuery.isSuccess && fetchOneUserQuery.data) {
       Alert.alert(
         t('screen_example.hello_user', { name: fetchOneUserQuery.data.name }),
       );

+ 29 - 0
src/screens/Home/Home.tsx

@@ -0,0 +1,29 @@
+import { useEffect } from 'react';
+import { View } from 'react-native';
+import { useFocusEffect } from '@react-navigation/native';
+
+import { useWebViewContext } from '@/App';
+import { useTheme } from '@/theme';
+
+function Home() {
+  const { layout } = useTheme();
+  const { setShowWebView } = useWebViewContext();
+
+  // 当Home页面获得焦点时显示WebView
+  useFocusEffect(() => {
+    setShowWebView(true);
+    
+    // 当页面失去焦点时隐藏WebView
+    return () => {
+      setShowWebView(false);
+    };
+  });
+
+  return (
+    <View style={[layout.flex_1]}>
+      {/* WebView现在由App组件管理,这里只需要一个占位容器 */}
+    </View>
+  );
+}
+
+export default Home;

+ 193 - 0
src/screens/Login/Login.tsx

@@ -0,0 +1,193 @@
+import type { RootScreenProps } from '@/navigation/types';
+import { Paths } from '@/navigation/paths';
+import { useTranslation } from 'react-i18next';
+import { Alert, Platform, ScrollView, Text, TouchableOpacity, View } from 'react-native';
+import { GoogleSignin } from '@react-native-google-signin/google-signin';
+import { appleAuth } from '@invertase/react-native-apple-authentication';
+import DeviceInfo from 'react-native-device-info';
+
+import { AUTH_CONFIG } from '@/config/auth';
+
+import { useI18n } from '@/hooks';
+import { useTheme } from '@/theme';
+
+import { AssetByVariant, IconByVariant, Skeleton } from '@/components/atoms';
+import { SafeScreen } from '@/components/templates';
+
+
+
+function Login({ navigation }: RootScreenProps<Paths.Login>) {
+  const { toggleLanguage } = useI18n();
+
+  const {
+    backgrounds,
+    changeTheme,
+    colors,
+    components,
+    fonts,
+    gutters,
+    layout,
+    variant,
+  } = useTheme();
+
+
+  const onChangeTheme = () => {
+    changeTheme(variant === 'default' ? 'dark' : 'default');
+  };
+
+  const onGoHome = () => {
+    navigation.navigate(Paths.Home);
+  }
+
+
+  const handleLogin = async () => {
+    try {
+      const deviceId = await DeviceInfo.getUniqueId();
+      console.log('设备ID:', deviceId);
+
+      if (Platform.OS === 'ios') {
+        // iOS使用Apple登录
+        await handleAppleLogin();
+      } else {
+        // Android使用Google登录
+        await handleGoogleLogin();
+      }
+    } catch (error) {
+      console.error('登录失败:', error);
+      Alert.alert('登录失败', '请稍后重试');
+    }
+  };
+
+  const handleAppleLogin = async () => {
+    try {
+      // 执行Apple登录请求
+      const appleAuthRequestResponse = await appleAuth.performRequest({
+        requestedOperation: appleAuth.Operation.LOGIN,
+        requestedScopes: [appleAuth.Scope.EMAIL, appleAuth.Scope.FULL_NAME],
+      });
+
+      // 获取凭证状态
+      const credentialState = await appleAuth.getCredentialStateForUser(
+        appleAuthRequestResponse.user,
+      );
+
+      if (credentialState === appleAuth.State.AUTHORIZED) {
+        console.log('Apple登录成功:', appleAuthRequestResponse);
+        Alert.alert('登录成功', `欢迎 ${appleAuthRequestResponse.fullName?.givenName || '用户'}`);
+      }
+    } catch (error) {
+      console.error('Apple登录失败:', error);
+      throw error;
+    }
+  };
+
+  const handleGoogleLogin = async () => {
+    try {
+      // 配置Google登录
+      await GoogleSignin.configure({
+        webClientId: AUTH_CONFIG.GOOGLE.WEB_CLIENT_ID,
+        offlineAccess: true,
+      });
+
+      // 检查是否已登录
+      await GoogleSignin.hasPlayServices();
+      
+      // 执行登录
+      const userInfo = await GoogleSignin.signIn();
+      console.log('Google登录成功:', userInfo);
+      Alert.alert('登录成功', `欢迎 ${userInfo.data?.user.name || '用户'}`);
+    } catch (error) {
+      console.error('Google登录失败:', error);
+      throw error;
+    }
+  };
+
+  return (
+    <SafeScreen>
+      <ScrollView>
+        <View
+          style={[
+            layout.justifyCenter,
+            layout.itemsCenter,
+            gutters.marginTop_80,
+          ]}
+        >
+          <View
+            style={[layout.relative, backgrounds.gray100, components.circle250]}
+          />
+
+          <View style={[layout.absolute, gutters.paddingTop_80]}>
+            <AssetByVariant
+              path="tom"
+              resizeMode="contain"
+              style={{ height: 300, width: 300 }}
+            />
+          </View>
+        </View>
+
+        <View style={[gutters.paddingHorizontal_32, gutters.marginTop_40]}>
+          <View style={[gutters.marginTop_40]}>
+            <Text style={[fonts.size_40, fonts.gray800, fonts.bold]}>
+              欢迎使用
+            </Text>
+            <Text
+              style={[fonts.size_16, fonts.gray200, gutters.marginBottom_40]}
+            >
+              {/* {t('screen_example.description')} */}
+            </Text>
+          </View>
+
+          <View
+            style={[
+              layout.row,
+              layout.justifyBetween,
+              layout.fullWidth,
+              gutters.marginTop_16,
+            ]}
+          >
+            <Skeleton
+              height={64}
+              loading={false}
+              style={{ borderRadius: components.buttonCircle.borderRadius }}
+              width={64}
+            >
+              <TouchableOpacity
+                onPress={handleLogin}
+                style={[components.buttonCircle, gutters.marginBottom_16]}
+                testID="fetch-user-button"
+              >
+                <IconByVariant path="user" stroke={colors.purple500} />
+              </TouchableOpacity>
+            </Skeleton>
+
+            <TouchableOpacity
+              onPress={onGoHome}
+              style={[components.buttonCircle, gutters.marginBottom_16]}
+              testID="change-theme-button"
+            >
+              <IconByVariant path="home" stroke={colors.purple500} />
+            </TouchableOpacity>
+
+            <TouchableOpacity
+              onPress={onChangeTheme}
+              style={[components.buttonCircle, gutters.marginBottom_16]}
+              testID="change-theme-button"
+            >
+              <IconByVariant path="theme" stroke={colors.purple500} />
+            </TouchableOpacity>
+
+            <TouchableOpacity
+              onPress={toggleLanguage}
+              style={[components.buttonCircle, gutters.marginBottom_16]}
+              testID="change-language-button"
+            >
+              <IconByVariant path="language" stroke={colors.purple500} />
+            </TouchableOpacity>
+          </View>
+        </View>
+      </ScrollView>
+    </SafeScreen>
+  );
+}
+
+export default Login;

+ 1 - 1
src/screens/Startup/Startup.tsx

@@ -26,7 +26,7 @@ function Startup({ navigation }: RootScreenProps<Paths.Startup>) {
     if (isSuccess) {
       navigation.reset({
         index: 0,
-        routes: [{ name: Paths.Example }],
+        routes: [{ name: Paths.Login }],
       });
     }
   }, [isSuccess, navigation]);

+ 2 - 0
src/screens/index.ts

@@ -1,2 +1,4 @@
 export { default as Example } from './Example/Example';
 export { default as Startup } from './Startup/Startup';
+export { default as Login } from './Login/Login';
+export { default as Home } from './Home/Home';

+ 4 - 0
src/theme/assets/icons/home.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
+  class="size-6">
+  <path d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" stroke-linecap="round" stroke-linejoin="round" />
+</svg>

+ 5 - 0
src/theme/assets/icons/user.svg

@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
+  class="size-6">
+  <path stroke-linecap="round" stroke-linejoin="round"
+    d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
+</svg>

+ 0 - 19
src/translations/fr-FR.json

@@ -1,19 +0,0 @@
-{
-  "recipemuse": {
-    "common_appName": {
-      "full": "recipemuse React Native",
-      "initials": "RNB"
-    },
-    "common_error": "Quelque chose s'est mal passé.",
-    "screen_example": {
-      "hello_user": "Salut, je m'appelle {{name}}",
-      "title": "Bienvenue sur Le $t(common_appName.full)",
-      "description": "Vous voulez découvrir des fonctionnalités ? Cliquez sur l'un des trois boutons en bas de l'écran. Le premier vous permet d'appeler une API REST. Le deuxième de changer la couleur du thème. Et le troisième de changer la langue"
-    },
-    "error_boundary": {
-      "title": "Oups ! Quelque chose s'est mal passé.",
-      "description": "Nous sommes désolés pour le désagrément. Veuillez réessayer plus tard.",
-      "cta": "Recharger l'écran"
-    }
-  }
-}

+ 7 - 5
src/translations/index.ts

@@ -6,19 +6,21 @@ import i18n from 'i18next';
 import { initReactI18next } from 'react-i18next';
 
 import en from './en-EN.json';
-import fr from './fr-FR.json';
+// import fr from './fr-FR.json';
+import zh from './zh-CN.json';
 
-export const defaultNS = 'recipemuse';
+export const defaultNS = 'boilerplate';
 
 export const resources = {
   'en-EN': en,
-  'fr-FR': fr,
+  // 'fr-FR': fr,
+  'zh-CN': zh,
 } as const satisfies Record<Language, unknown>;
 
 void i18n.use(initReactI18next).init({
   defaultNS,
-  fallbackLng: 'fr-FR',
-  lng: 'fr-FR',
+  fallbackLng: 'zh-CN',
+  lng: 'zh-CN',
   resources,
 });
 

+ 19 - 0
src/translations/zh-CN.json

@@ -0,0 +1,19 @@
+{
+  "boilerplate": {
+    "common_appName": {
+      "full": "React Native 脚手架",
+      "initials": "RNB"
+    },
+    "common_error": "哎呀!出了点问题。",
+    "screen_example": {
+      "hello_user": "你好,我的名字是 {{name}}",
+      "title": "欢迎使用 $t(common_appName.full)",
+      "description": "想要探索一些功能吗?只需点击屏幕底部的三个按钮之一。第一个按钮允许您调用 REST API。第二个按钮让您更改主题颜色。第三个按钮允许您更改语言。"
+    },
+    "error_boundary": {
+      "title": "哎呀!出了点问题。",
+      "description": "很抱歉给您带来不便。请稍后再试。",
+      "cta": "重新加载屏幕"
+    }
+  }
+}