Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5704531f3a | ||
|
|
87819780c9 | ||
|
|
3b88b9cde9 | ||
|
|
439edf827c | ||
|
|
24fa66281c | ||
|
|
99fbad3e97 | ||
|
|
dcdab3cff7 | ||
|
|
1cdce616e4 | ||
|
|
212607fc82 | ||
|
|
0cf0a41a60 | ||
|
|
f863004c8d | ||
|
|
61c56c7adb | ||
|
|
413a9603a4 | ||
|
|
f87b13cc05 | ||
|
|
0e0415eec4 | ||
|
|
7ccbc5568d | ||
|
|
787a166263 | ||
|
|
74f7370aab | ||
|
|
1b2637f0ee | ||
|
|
caeeb9a1cf | ||
|
|
e7f1d777dd |
@@ -1,13 +1,23 @@
|
||||
platform :ios, '16.0'
|
||||
|
||||
project 'suixinkan.xcodeproj',
|
||||
'Debug' => :debug,
|
||||
'SimulatorDebug' => :debug,
|
||||
'Release' => :release
|
||||
|
||||
target 'suixinkan' do
|
||||
use_frameworks!
|
||||
pod 'AMapLocation-NO-IDFA'
|
||||
pod 'AMap3DMap-NO-IDFA'
|
||||
pod 'AMapSearch-NO-IDFA'
|
||||
# 高德 SDK 未提供 arm64-simulator slice,仅在真机配置中接入。
|
||||
pod 'AMapFoundation-NO-IDFA', :configurations => ['Debug', 'Release']
|
||||
pod 'AMapLocation-NO-IDFA', :configurations => ['Debug', 'Release']
|
||||
pod 'AMap3DMap-NO-IDFA', :configurations => ['Debug', 'Release']
|
||||
pod 'AMapSearch-NO-IDFA', :configurations => ['Debug', 'Release']
|
||||
pod 'UMCommon'
|
||||
pod 'UMDevice'
|
||||
pod 'UMAPM'
|
||||
# 极光推送:固定无 IDFA 的 JCore,避免引入 ATT 授权。
|
||||
pod 'JCore', '5.4.2-noidfa'
|
||||
pod 'JPush', '6.1.0'
|
||||
pod 'IQKeyboardManagerSwift'
|
||||
# 微信 Open SDK:仅接入分享能力,不调用支付 API。
|
||||
pod 'WechatOpenSDK-XCFramework', '~> 2.0.4'
|
||||
@@ -21,6 +31,9 @@ post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
|
||||
if target.name == 'Pods-suixinkanTests' && config.name == 'SimulatorDebug'
|
||||
config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = ''
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -46,6 +46,9 @@ PODS:
|
||||
- IQKeyboardCore
|
||||
- IQTextView (1.0.5):
|
||||
- IQKeyboardToolbar/Placeholderable
|
||||
- JCore (5.4.2-noidfa)
|
||||
- JPush (6.1.0):
|
||||
- JCore (> 5.1.1)
|
||||
- UMAPM (2.0.7):
|
||||
- UMCommon
|
||||
- UMCommon (7.5.11):
|
||||
@@ -55,9 +58,12 @@ PODS:
|
||||
|
||||
DEPENDENCIES:
|
||||
- AMap3DMap-NO-IDFA
|
||||
- AMapFoundation-NO-IDFA
|
||||
- AMapLocation-NO-IDFA
|
||||
- AMapSearch-NO-IDFA
|
||||
- IQKeyboardManagerSwift
|
||||
- JCore (= 5.4.2-noidfa)
|
||||
- JPush (= 6.1.0)
|
||||
- UMAPM
|
||||
- UMCommon
|
||||
- UMDevice
|
||||
@@ -77,6 +83,8 @@ SPEC REPOS:
|
||||
- IQKeyboardToolbarManager
|
||||
- IQTextInputViewNotification
|
||||
- IQTextView
|
||||
- JCore
|
||||
- JPush
|
||||
- UMAPM
|
||||
- UMCommon
|
||||
- UMDevice
|
||||
@@ -95,11 +103,13 @@ SPEC CHECKSUMS:
|
||||
IQKeyboardToolbarManager: 242e858cc74e06e00aa989bea1355b24d4397854
|
||||
IQTextInputViewNotification: 3b9fb27a16e7ee8958cc9092cfb07a1a9e1fd559
|
||||
IQTextView: ae13b4922f22e6f027f62c557d9f4f236b19d5c7
|
||||
JCore: 696e0ac22f417bb0ac4729fc41bc2941aef14e2b
|
||||
JPush: 891c497687be788b094bde368d51289355ee1112
|
||||
UMAPM: ba9fbebe8dd2048b251c332ab0afc86e1ba5ea63
|
||||
UMCommon: d652b3b372a801b36db203f5b546ab3e15676898
|
||||
UMDevice: 20b7b3c37a36b2c5c7ca8b5a54386ff9ed2b3b19
|
||||
WechatOpenSDK-XCFramework: ff342ae616bb86df3d236aca38059dfd4bc4a949
|
||||
|
||||
PODFILE CHECKSUM: 0fd38f741cbaae39c8e7a70fa233f651d3b34d0f
|
||||
PODFILE CHECKSUM: 5abf70ade4a9c4b66b64877fdea91f1d086bcaf5
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# jcore-sdk
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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>AvailableLibraries</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>HeadersPath</key>
|
||||
<string>Headers</string>
|
||||
<key>LibraryIdentifier</key>
|
||||
<string>ios-arm64</string>
|
||||
<key>LibraryPath</key>
|
||||
<string>libJCore.a</string>
|
||||
<key>SupportedArchitectures</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>SupportedPlatform</key>
|
||||
<string>ios</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>HeadersPath</key>
|
||||
<string>Headers</string>
|
||||
<key>LibraryIdentifier</key>
|
||||
<string>ios-arm64_x86_64-simulator</string>
|
||||
<key>LibraryPath</key>
|
||||
<string>libJCore.a</string>
|
||||
<key>SupportedArchitectures</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
<string>x86_64</string>
|
||||
</array>
|
||||
<key>SupportedPlatform</key>
|
||||
<string>ios</string>
|
||||
<key>SupportedPlatformVariant</key>
|
||||
<string>simulator</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XFWK</string>
|
||||
<key>XCFrameworkFormatVersion</key>
|
||||
<string>1.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// JGCOREService.h
|
||||
// JCore
|
||||
//
|
||||
// Created by Shuni Huang on 2024/3/19.
|
||||
// Copyright © 2024 jiguang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface JGCOREAPI : NSObject
|
||||
|
||||
+ (void)ci:(BOOL)enable;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// JGInforCollectionAuth.h
|
||||
// JCore
|
||||
//
|
||||
// Created by 豆瓣 on 2021/10/27.
|
||||
// Copyright © 2021 jiguang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 合规接口,是否授权极光采集一定的设备信息
|
||||
@interface JGInforCollectionAuthItems : NSObject
|
||||
/// 是否授权,默认YES
|
||||
@property(nonatomic,assign)BOOL isAuth;
|
||||
@end
|
||||
|
||||
@interface JGInforCollectionAuth : NSObject
|
||||
|
||||
/// 设备信息采集授权接口(合规接口)
|
||||
/// 请务必在调用初始化、功能性接口前调用此接口进行合规授权
|
||||
/// @param authBlock auth:YES 则极光认为您同意极光采集一定的设备信息
|
||||
+(void)JCollectionAuth:(void(^_Nullable)(JGInforCollectionAuthItems *authInfo))authBlock;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,64 @@
|
||||
<?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>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeCrashData</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypePreciseLocation</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>E174.1</string>
|
||||
</array>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
</array>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// JGCOREService.h
|
||||
// JCore
|
||||
//
|
||||
// Created by Shuni Huang on 2024/3/19.
|
||||
// Copyright © 2024 jiguang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface JGCOREAPI : NSObject
|
||||
|
||||
+ (void)ci:(BOOL)enable;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// JGInforCollectionAuth.h
|
||||
// JCore
|
||||
//
|
||||
// Created by 豆瓣 on 2021/10/27.
|
||||
// Copyright © 2021 jiguang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 合规接口,是否授权极光采集一定的设备信息
|
||||
@interface JGInforCollectionAuthItems : NSObject
|
||||
/// 是否授权,默认YES
|
||||
@property(nonatomic,assign)BOOL isAuth;
|
||||
@end
|
||||
|
||||
@interface JGInforCollectionAuth : NSObject
|
||||
|
||||
/// 设备信息采集授权接口(合规接口)
|
||||
/// 请务必在调用初始化、功能性接口前调用此接口进行合规授权
|
||||
/// @param authBlock auth:YES 则极光认为您同意极光采集一定的设备信息
|
||||
+(void)JCollectionAuth:(void(^_Nullable)(JGInforCollectionAuthItems *authInfo))authBlock;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,64 @@
|
||||
<?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>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeCrashData</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypePreciseLocation</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>E174.1</string>
|
||||
</array>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
</array>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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>AvailableLibraries</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>HeadersPath</key>
|
||||
<string>Headers</string>
|
||||
<key>LibraryIdentifier</key>
|
||||
<string>ios-arm64_x86_64-simulator</string>
|
||||
<key>LibraryPath</key>
|
||||
<string>libJPush.a</string>
|
||||
<key>SupportedArchitectures</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
<string>x86_64</string>
|
||||
</array>
|
||||
<key>SupportedPlatform</key>
|
||||
<string>ios</string>
|
||||
<key>SupportedPlatformVariant</key>
|
||||
<string>simulator</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>HeadersPath</key>
|
||||
<string>Headers</string>
|
||||
<key>LibraryIdentifier</key>
|
||||
<string>ios-arm64</string>
|
||||
<key>LibraryPath</key>
|
||||
<string>libJPush.a</string>
|
||||
<key>SupportedArchitectures</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>SupportedPlatform</key>
|
||||
<string>ios</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XFWK</string>
|
||||
<key>XCFrameworkFormatVersion</key>
|
||||
<string>1.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -46,6 +46,9 @@ PODS:
|
||||
- IQKeyboardCore
|
||||
- IQTextView (1.0.5):
|
||||
- IQKeyboardToolbar/Placeholderable
|
||||
- JCore (5.4.2-noidfa)
|
||||
- JPush (6.1.0):
|
||||
- JCore (> 5.1.1)
|
||||
- UMAPM (2.0.7):
|
||||
- UMCommon
|
||||
- UMCommon (7.5.11):
|
||||
@@ -55,9 +58,12 @@ PODS:
|
||||
|
||||
DEPENDENCIES:
|
||||
- AMap3DMap-NO-IDFA
|
||||
- AMapFoundation-NO-IDFA
|
||||
- AMapLocation-NO-IDFA
|
||||
- AMapSearch-NO-IDFA
|
||||
- IQKeyboardManagerSwift
|
||||
- JCore (= 5.4.2-noidfa)
|
||||
- JPush (= 6.1.0)
|
||||
- UMAPM
|
||||
- UMCommon
|
||||
- UMDevice
|
||||
@@ -77,6 +83,8 @@ SPEC REPOS:
|
||||
- IQKeyboardToolbarManager
|
||||
- IQTextInputViewNotification
|
||||
- IQTextView
|
||||
- JCore
|
||||
- JPush
|
||||
- UMAPM
|
||||
- UMCommon
|
||||
- UMDevice
|
||||
@@ -95,11 +103,13 @@ SPEC CHECKSUMS:
|
||||
IQKeyboardToolbarManager: 242e858cc74e06e00aa989bea1355b24d4397854
|
||||
IQTextInputViewNotification: 3b9fb27a16e7ee8958cc9092cfb07a1a9e1fd559
|
||||
IQTextView: ae13b4922f22e6f027f62c557d9f4f236b19d5c7
|
||||
JCore: 696e0ac22f417bb0ac4729fc41bc2941aef14e2b
|
||||
JPush: 891c497687be788b094bde368d51289355ee1112
|
||||
UMAPM: ba9fbebe8dd2048b251c332ab0afc86e1ba5ea63
|
||||
UMCommon: d652b3b372a801b36db203f5b546ab3e15676898
|
||||
UMDevice: 20b7b3c37a36b2c5c7ca8b5a54386ff9ed2b3b19
|
||||
WechatOpenSDK-XCFramework: ff342ae616bb86df3d236aca38059dfd4bc4a949
|
||||
|
||||
PODFILE CHECKSUM: 0fd38f741cbaae39c8e7a70fa233f651d3b34d0f
|
||||
PODFILE CHECKSUM: 5abf70ade4a9c4b66b64877fdea91f1d086bcaf5
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
${PODS_ROOT}/Target Support Files/JCore/JCore-xcframeworks.sh
|
||||
${PODS_ROOT}/JCore/jcore-noidfa-ios-5.4.2.xcframework
|
||||
@@ -0,0 +1 @@
|
||||
${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore/jcore-noidfa-ios-5.4.2.framework
|
||||
@@ -0,0 +1,121 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
function on_error {
|
||||
echo "$(realpath -mq "${0}"):$1: error: Unexpected failure"
|
||||
}
|
||||
trap 'on_error $LINENO' ERR
|
||||
|
||||
|
||||
# This protects against multiple targets copying the same framework dependency at the same time. The solution
|
||||
# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
|
||||
RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
|
||||
|
||||
|
||||
variant_for_slice()
|
||||
{
|
||||
case "$1" in
|
||||
"jcore-noidfa-ios-5.4.2.xcframework/ios-arm64")
|
||||
echo ""
|
||||
;;
|
||||
"jcore-noidfa-ios-5.4.2.xcframework/ios-arm64_x86_64-simulator")
|
||||
echo "simulator"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
archs_for_slice()
|
||||
{
|
||||
case "$1" in
|
||||
"jcore-noidfa-ios-5.4.2.xcframework/ios-arm64")
|
||||
echo "arm64"
|
||||
;;
|
||||
"jcore-noidfa-ios-5.4.2.xcframework/ios-arm64_x86_64-simulator")
|
||||
echo "arm64 x86_64"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
copy_dir()
|
||||
{
|
||||
local source="$1"
|
||||
local destination="$2"
|
||||
|
||||
# Use filter instead of exclude so missing patterns don't throw errors.
|
||||
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" \"${source}*\" \"${destination}\""
|
||||
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" "${source}"/* "${destination}"
|
||||
}
|
||||
|
||||
SELECT_SLICE_RETVAL=""
|
||||
|
||||
select_slice() {
|
||||
local xcframework_name="$1"
|
||||
xcframework_name="${xcframework_name##*/}"
|
||||
local paths=("${@:2}")
|
||||
# Locate the correct slice of the .xcframework for the current architectures
|
||||
local target_path=""
|
||||
|
||||
# Split archs on space so we can find a slice that has all the needed archs
|
||||
local target_archs=$(echo $ARCHS | tr " " "\n")
|
||||
|
||||
local target_variant=""
|
||||
if [[ "$PLATFORM_NAME" == *"simulator" ]]; then
|
||||
target_variant="simulator"
|
||||
fi
|
||||
if [[ ! -z ${EFFECTIVE_PLATFORM_NAME+x} && "$EFFECTIVE_PLATFORM_NAME" == *"maccatalyst" ]]; then
|
||||
target_variant="maccatalyst"
|
||||
fi
|
||||
for i in ${!paths[@]}; do
|
||||
local matched_all_archs="1"
|
||||
local slice_archs="$(archs_for_slice "${xcframework_name}/${paths[$i]}")"
|
||||
local slice_variant="$(variant_for_slice "${xcframework_name}/${paths[$i]}")"
|
||||
for target_arch in $target_archs; do
|
||||
if ! [[ "${slice_variant}" == "$target_variant" ]]; then
|
||||
matched_all_archs="0"
|
||||
break
|
||||
fi
|
||||
|
||||
if ! echo "${slice_archs}" | tr " " "\n" | grep -F -q -x "$target_arch"; then
|
||||
matched_all_archs="0"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$matched_all_archs" == "1" ]]; then
|
||||
# Found a matching slice
|
||||
echo "Selected xcframework slice ${paths[$i]}"
|
||||
SELECT_SLICE_RETVAL=${paths[$i]}
|
||||
break
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
install_xcframework() {
|
||||
local basepath="$1"
|
||||
local name="$2"
|
||||
local package_type="$3"
|
||||
local paths=("${@:4}")
|
||||
|
||||
# Locate the correct slice of the .xcframework for the current architectures
|
||||
select_slice "${basepath}" "${paths[@]}"
|
||||
local target_path="$SELECT_SLICE_RETVAL"
|
||||
if [[ -z "$target_path" ]]; then
|
||||
echo "warning: [CP] $(basename ${basepath}): Unable to find matching slice in '${paths[@]}' for the current build architectures ($ARCHS) and platform (${EFFECTIVE_PLATFORM_NAME-${PLATFORM_NAME}})."
|
||||
return
|
||||
fi
|
||||
local source="$basepath/$target_path"
|
||||
|
||||
local destination="${PODS_XCFRAMEWORKS_BUILD_DIR}/${name}"
|
||||
|
||||
if [ ! -d "$destination" ]; then
|
||||
mkdir -p "$destination"
|
||||
fi
|
||||
|
||||
copy_dir "$source/" "$destination"
|
||||
echo "Copied $source to $destination"
|
||||
}
|
||||
|
||||
install_xcframework "${PODS_ROOT}/JCore/jcore-noidfa-ios-5.4.2.xcframework" "JCore" "library" "ios-arm64" "ios-arm64_x86_64-simulator"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/JCore
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/JCore"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore/Headers"
|
||||
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore"
|
||||
OTHER_LDFLAGS = $(inherited) -l"JCore" -l"resolv" -l"z" -framework "CFNetwork" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreTelephony" -framework "Foundation" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "WebKit" -weak_framework "Network" -weak_framework "UserNotifications"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/JCore
|
||||
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
VALID_ARCHS = x86_64 arm64
|
||||
@@ -0,0 +1,17 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/JCore
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/JCore"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore/Headers"
|
||||
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore"
|
||||
OTHER_LDFLAGS = $(inherited) -l"JCore" -l"resolv" -l"z" -framework "CFNetwork" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreTelephony" -framework "Foundation" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "WebKit" -weak_framework "Network" -weak_framework "UserNotifications"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/JCore
|
||||
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
VALID_ARCHS = x86_64 arm64
|
||||
@@ -0,0 +1,2 @@
|
||||
${PODS_ROOT}/Target Support Files/JPush/JPush-xcframeworks.sh
|
||||
${PODS_ROOT}/JPush/jpush-ios-6.1.0.xcframework
|
||||
@@ -0,0 +1 @@
|
||||
${PODS_XCFRAMEWORKS_BUILD_DIR}/JPush/jpush-ios-6.1.0.framework
|
||||
@@ -0,0 +1,121 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
function on_error {
|
||||
echo "$(realpath -mq "${0}"):$1: error: Unexpected failure"
|
||||
}
|
||||
trap 'on_error $LINENO' ERR
|
||||
|
||||
|
||||
# This protects against multiple targets copying the same framework dependency at the same time. The solution
|
||||
# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
|
||||
RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
|
||||
|
||||
|
||||
variant_for_slice()
|
||||
{
|
||||
case "$1" in
|
||||
"jpush-ios-6.1.0.xcframework/ios-arm64")
|
||||
echo ""
|
||||
;;
|
||||
"jpush-ios-6.1.0.xcframework/ios-arm64_x86_64-simulator")
|
||||
echo "simulator"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
archs_for_slice()
|
||||
{
|
||||
case "$1" in
|
||||
"jpush-ios-6.1.0.xcframework/ios-arm64")
|
||||
echo "arm64"
|
||||
;;
|
||||
"jpush-ios-6.1.0.xcframework/ios-arm64_x86_64-simulator")
|
||||
echo "arm64 x86_64"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
copy_dir()
|
||||
{
|
||||
local source="$1"
|
||||
local destination="$2"
|
||||
|
||||
# Use filter instead of exclude so missing patterns don't throw errors.
|
||||
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" \"${source}*\" \"${destination}\""
|
||||
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" "${source}"/* "${destination}"
|
||||
}
|
||||
|
||||
SELECT_SLICE_RETVAL=""
|
||||
|
||||
select_slice() {
|
||||
local xcframework_name="$1"
|
||||
xcframework_name="${xcframework_name##*/}"
|
||||
local paths=("${@:2}")
|
||||
# Locate the correct slice of the .xcframework for the current architectures
|
||||
local target_path=""
|
||||
|
||||
# Split archs on space so we can find a slice that has all the needed archs
|
||||
local target_archs=$(echo $ARCHS | tr " " "\n")
|
||||
|
||||
local target_variant=""
|
||||
if [[ "$PLATFORM_NAME" == *"simulator" ]]; then
|
||||
target_variant="simulator"
|
||||
fi
|
||||
if [[ ! -z ${EFFECTIVE_PLATFORM_NAME+x} && "$EFFECTIVE_PLATFORM_NAME" == *"maccatalyst" ]]; then
|
||||
target_variant="maccatalyst"
|
||||
fi
|
||||
for i in ${!paths[@]}; do
|
||||
local matched_all_archs="1"
|
||||
local slice_archs="$(archs_for_slice "${xcframework_name}/${paths[$i]}")"
|
||||
local slice_variant="$(variant_for_slice "${xcframework_name}/${paths[$i]}")"
|
||||
for target_arch in $target_archs; do
|
||||
if ! [[ "${slice_variant}" == "$target_variant" ]]; then
|
||||
matched_all_archs="0"
|
||||
break
|
||||
fi
|
||||
|
||||
if ! echo "${slice_archs}" | tr " " "\n" | grep -F -q -x "$target_arch"; then
|
||||
matched_all_archs="0"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$matched_all_archs" == "1" ]]; then
|
||||
# Found a matching slice
|
||||
echo "Selected xcframework slice ${paths[$i]}"
|
||||
SELECT_SLICE_RETVAL=${paths[$i]}
|
||||
break
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
install_xcframework() {
|
||||
local basepath="$1"
|
||||
local name="$2"
|
||||
local package_type="$3"
|
||||
local paths=("${@:4}")
|
||||
|
||||
# Locate the correct slice of the .xcframework for the current architectures
|
||||
select_slice "${basepath}" "${paths[@]}"
|
||||
local target_path="$SELECT_SLICE_RETVAL"
|
||||
if [[ -z "$target_path" ]]; then
|
||||
echo "warning: [CP] $(basename ${basepath}): Unable to find matching slice in '${paths[@]}' for the current build architectures ($ARCHS) and platform (${EFFECTIVE_PLATFORM_NAME-${PLATFORM_NAME}})."
|
||||
return
|
||||
fi
|
||||
local source="$basepath/$target_path"
|
||||
|
||||
local destination="${PODS_XCFRAMEWORKS_BUILD_DIR}/${name}"
|
||||
|
||||
if [ ! -d "$destination" ]; then
|
||||
mkdir -p "$destination"
|
||||
fi
|
||||
|
||||
copy_dir "$source/" "$destination"
|
||||
echo "Copied $source to $destination"
|
||||
}
|
||||
|
||||
install_xcframework "${PODS_ROOT}/JPush/jpush-ios-6.1.0.xcframework" "JPush" "library" "ios-arm64" "ios-arm64_x86_64-simulator"
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/JPush
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/JCore" "${PODS_ROOT}/JPush"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JPush/Headers"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/JPush
|
||||
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
SWIFT_INCLUDE_PATHS = $(inherited) "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore"
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
@@ -0,0 +1,15 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/JPush
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/JCore" "${PODS_ROOT}/JPush"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JPush/Headers"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/JPush
|
||||
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
SWIFT_INCLUDE_PATHS = $(inherited) "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore"
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
@@ -219,6 +219,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
## JCore
|
||||
|
||||
Copyright jpush.cn
|
||||
|
||||
## JPush
|
||||
|
||||
Copyright jiguang.cn
|
||||
|
||||
## UMAPM
|
||||
|
||||
Copyright 2011 - 2020 umeng.com. All rights reserved.
|
||||
|
||||
@@ -302,6 +302,26 @@ SOFTWARE.
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Copyright jpush.cn</string>
|
||||
<key>License</key>
|
||||
<string>Copyright</string>
|
||||
<key>Title</key>
|
||||
<string>JCore</string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Copyright jiguang.cn</string>
|
||||
<key>License</key>
|
||||
<string>Copyright</string>
|
||||
<key>Title</key>
|
||||
<string>JPush</string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Copyright 2011 - 2020 umeng.com. All rights reserved.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
${PODS_ROOT}/Target Support Files/Pods-suixinkan/Pods-suixinkan-frameworks.sh
|
||||
${BUILT_PRODUCTS_DIR}/IQKeyboardCore/IQKeyboardCore.framework
|
||||
${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework
|
||||
${BUILT_PRODUCTS_DIR}/IQKeyboardNotification/IQKeyboardNotification.framework
|
||||
${BUILT_PRODUCTS_DIR}/IQKeyboardReturnManager/IQKeyboardReturnManager.framework
|
||||
${BUILT_PRODUCTS_DIR}/IQKeyboardToolbar/IQKeyboardToolbar.framework
|
||||
${BUILT_PRODUCTS_DIR}/IQKeyboardToolbarManager/IQKeyboardToolbarManager.framework
|
||||
${BUILT_PRODUCTS_DIR}/IQTextInputViewNotification/IQTextInputViewNotification.framework
|
||||
${BUILT_PRODUCTS_DIR}/IQTextView/IQTextView.framework
|
||||
@@ -0,0 +1,8 @@
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardCore.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardNotification.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardReturnManager.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardToolbar.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardToolbarManager.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQTextInputViewNotification.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQTextView.framework
|
||||
@@ -195,6 +195,16 @@ if [[ "$CONFIGURATION" == "Release" ]]; then
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/IQTextInputViewNotification/IQTextInputViewNotification.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/IQTextView/IQTextView.framework"
|
||||
fi
|
||||
if [[ "$CONFIGURATION" == "SimulatorDebug" ]]; then
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardCore/IQKeyboardCore.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardNotification/IQKeyboardNotification.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardReturnManager/IQKeyboardReturnManager.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardToolbar/IQKeyboardToolbar.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardToolbarManager/IQKeyboardToolbarManager.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/IQTextInputViewNotification/IQTextInputViewNotification.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/IQTextView/IQTextView.framework"
|
||||
fi
|
||||
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
|
||||
wait
|
||||
fi
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
${PODS_ROOT}/Target Support Files/Pods-suixinkan/Pods-suixinkan-resources.sh
|
||||
@@ -3,13 +3,13 @@ ARCHS = $(ARCHS_STANDARD)
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO
|
||||
EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView" "${PODS_ROOT}/AMap3DMap-NO-IDFA" "${PODS_ROOT}/AMapFoundation-NO-IDFA" "${PODS_ROOT}/AMapLocation-NO-IDFA" "${PODS_ROOT}/AMapSearch-NO-IDFA" "${PODS_ROOT}/UMAPM/UMAPM_2.0.7" "${PODS_ROOT}/UMCommon/UMCommon_7.5.11" "${PODS_ROOT}/UMDevice/UMDevice_3.6.0" "${PODS_ROOT}/WechatOpenSDK-XCFramework" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMAPM" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMCommon" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMDevice" "${PODS_XCFRAMEWORKS_BUILD_DIR}/WechatOpenSDK-XCFramework"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView" "${PODS_ROOT}/AMap3DMap-NO-IDFA" "${PODS_ROOT}/AMapFoundation-NO-IDFA" "${PODS_ROOT}/AMapLocation-NO-IDFA" "${PODS_ROOT}/AMapSearch-NO-IDFA" "${PODS_ROOT}/JCore" "${PODS_ROOT}/JPush" "${PODS_ROOT}/UMAPM/UMAPM_2.0.7" "${PODS_ROOT}/UMCommon/UMCommon_7.5.11" "${PODS_ROOT}/UMDevice/UMDevice_3.6.0" "${PODS_ROOT}/WechatOpenSDK-XCFramework" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMAPM" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMCommon" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMDevice" "${PODS_XCFRAMEWORKS_BUILD_DIR}/WechatOpenSDK-XCFramework"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore/IQKeyboardCore.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification/IQKeyboardNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager/IQKeyboardReturnManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar/IQKeyboardToolbar.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager/IQKeyboardToolbarManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification/IQTextInputViewNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView/IQTextView.framework/Headers"
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore/IQKeyboardCore.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification/IQKeyboardNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager/IQKeyboardReturnManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar/IQKeyboardToolbar.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager/IQKeyboardToolbarManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification/IQTextInputViewNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView/IQTextView.framework/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JPush/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
|
||||
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"sqlite3" -l"sqlite3.0" -l"z" -framework "AMapFoundationKit" -framework "AMapLocationKit" -framework "AMapSearchKit" -framework "Combine" -framework "CoreGraphics" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "ExternalAccessory" -framework "GLKit" -framework "IQKeyboardCore" -framework "IQKeyboardManagerSwift" -framework "IQKeyboardNotification" -framework "IQKeyboardReturnManager" -framework "IQKeyboardToolbar" -framework "IQKeyboardToolbarManager" -framework "IQTextInputViewNotification" -framework "IQTextView" -framework "MAMapKit" -framework "OpenGLES" -framework "QuartzCore" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "UMAPM" -framework "UMCommon" -framework "UMDevice" -framework "WebKit" -framework "WechatOpenSDK"
|
||||
OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/AMap3DMap-NO-IDFA" "-F${PODS_CONFIGURATION_BUILD_DIR}/AMapFoundation-NO-IDFA" "-F${PODS_CONFIGURATION_BUILD_DIR}/AMapLocation-NO-IDFA" "-F${PODS_CONFIGURATION_BUILD_DIR}/AMapSearch-NO-IDFA" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQTextView" "-F${PODS_CONFIGURATION_BUILD_DIR}/UMAPM" "-F${PODS_CONFIGURATION_BUILD_DIR}/UMCommon" "-F${PODS_CONFIGURATION_BUILD_DIR}/UMDevice" "-F${PODS_CONFIGURATION_BUILD_DIR}/WechatOpenSDK-XCFramework"
|
||||
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JPush" "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
|
||||
OTHER_LDFLAGS = $(inherited) -ObjC -l"JCore" -l"JPush" -l"c++" -l"resolv" -l"sqlite3" -l"sqlite3.0" -l"z" -framework "AMapFoundationKit" -framework "AMapLocationKit" -framework "AMapSearchKit" -framework "CFNetwork" -framework "Combine" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "ExternalAccessory" -framework "Foundation" -framework "GLKit" -framework "IQKeyboardCore" -framework "IQKeyboardManagerSwift" -framework "IQKeyboardNotification" -framework "IQKeyboardReturnManager" -framework "IQKeyboardToolbar" -framework "IQKeyboardToolbarManager" -framework "IQTextInputViewNotification" -framework "IQTextView" -framework "MAMapKit" -framework "OpenGLES" -framework "QuartzCore" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "UMAPM" -framework "UMCommon" -framework "UMDevice" -framework "WebKit" -framework "WechatOpenSDK" -weak_framework "Network" -weak_framework "UserNotifications"
|
||||
OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/AMap3DMap-NO-IDFA" "-F${PODS_CONFIGURATION_BUILD_DIR}/AMapFoundation-NO-IDFA" "-F${PODS_CONFIGURATION_BUILD_DIR}/AMapLocation-NO-IDFA" "-F${PODS_CONFIGURATION_BUILD_DIR}/AMapSearch-NO-IDFA" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQTextView" "-F${PODS_CONFIGURATION_BUILD_DIR}/JCore" "-F${PODS_CONFIGURATION_BUILD_DIR}/JPush" "-F${PODS_CONFIGURATION_BUILD_DIR}/UMAPM" "-F${PODS_CONFIGURATION_BUILD_DIR}/UMCommon" "-F${PODS_CONFIGURATION_BUILD_DIR}/UMDevice" "-F${PODS_CONFIGURATION_BUILD_DIR}/WechatOpenSDK-XCFramework"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
|
||||
@@ -3,13 +3,13 @@ ARCHS = $(ARCHS_STANDARD)
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO
|
||||
EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView" "${PODS_ROOT}/AMap3DMap-NO-IDFA" "${PODS_ROOT}/AMapFoundation-NO-IDFA" "${PODS_ROOT}/AMapLocation-NO-IDFA" "${PODS_ROOT}/AMapSearch-NO-IDFA" "${PODS_ROOT}/UMAPM/UMAPM_2.0.7" "${PODS_ROOT}/UMCommon/UMCommon_7.5.11" "${PODS_ROOT}/UMDevice/UMDevice_3.6.0" "${PODS_ROOT}/WechatOpenSDK-XCFramework" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMAPM" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMCommon" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMDevice" "${PODS_XCFRAMEWORKS_BUILD_DIR}/WechatOpenSDK-XCFramework"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView" "${PODS_ROOT}/AMap3DMap-NO-IDFA" "${PODS_ROOT}/AMapFoundation-NO-IDFA" "${PODS_ROOT}/AMapLocation-NO-IDFA" "${PODS_ROOT}/AMapSearch-NO-IDFA" "${PODS_ROOT}/JCore" "${PODS_ROOT}/JPush" "${PODS_ROOT}/UMAPM/UMAPM_2.0.7" "${PODS_ROOT}/UMCommon/UMCommon_7.5.11" "${PODS_ROOT}/UMDevice/UMDevice_3.6.0" "${PODS_ROOT}/WechatOpenSDK-XCFramework" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMAPM" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMCommon" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMDevice" "${PODS_XCFRAMEWORKS_BUILD_DIR}/WechatOpenSDK-XCFramework"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore/IQKeyboardCore.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification/IQKeyboardNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager/IQKeyboardReturnManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar/IQKeyboardToolbar.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager/IQKeyboardToolbarManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification/IQTextInputViewNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView/IQTextView.framework/Headers"
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore/IQKeyboardCore.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification/IQKeyboardNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager/IQKeyboardReturnManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar/IQKeyboardToolbar.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager/IQKeyboardToolbarManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification/IQTextInputViewNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView/IQTextView.framework/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JPush/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
|
||||
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"sqlite3" -l"sqlite3.0" -l"z" -framework "AMapFoundationKit" -framework "AMapLocationKit" -framework "AMapSearchKit" -framework "Combine" -framework "CoreGraphics" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "ExternalAccessory" -framework "GLKit" -framework "IQKeyboardCore" -framework "IQKeyboardManagerSwift" -framework "IQKeyboardNotification" -framework "IQKeyboardReturnManager" -framework "IQKeyboardToolbar" -framework "IQKeyboardToolbarManager" -framework "IQTextInputViewNotification" -framework "IQTextView" -framework "MAMapKit" -framework "OpenGLES" -framework "QuartzCore" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "UMAPM" -framework "UMCommon" -framework "UMDevice" -framework "WebKit" -framework "WechatOpenSDK"
|
||||
OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/AMap3DMap-NO-IDFA" "-F${PODS_CONFIGURATION_BUILD_DIR}/AMapFoundation-NO-IDFA" "-F${PODS_CONFIGURATION_BUILD_DIR}/AMapLocation-NO-IDFA" "-F${PODS_CONFIGURATION_BUILD_DIR}/AMapSearch-NO-IDFA" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQTextView" "-F${PODS_CONFIGURATION_BUILD_DIR}/UMAPM" "-F${PODS_CONFIGURATION_BUILD_DIR}/UMCommon" "-F${PODS_CONFIGURATION_BUILD_DIR}/UMDevice" "-F${PODS_CONFIGURATION_BUILD_DIR}/WechatOpenSDK-XCFramework"
|
||||
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JPush" "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
|
||||
OTHER_LDFLAGS = $(inherited) -ObjC -l"JCore" -l"JPush" -l"c++" -l"resolv" -l"sqlite3" -l"sqlite3.0" -l"z" -framework "AMapFoundationKit" -framework "AMapLocationKit" -framework "AMapSearchKit" -framework "CFNetwork" -framework "Combine" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "ExternalAccessory" -framework "Foundation" -framework "GLKit" -framework "IQKeyboardCore" -framework "IQKeyboardManagerSwift" -framework "IQKeyboardNotification" -framework "IQKeyboardReturnManager" -framework "IQKeyboardToolbar" -framework "IQKeyboardToolbarManager" -framework "IQTextInputViewNotification" -framework "IQTextView" -framework "MAMapKit" -framework "OpenGLES" -framework "QuartzCore" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "UMAPM" -framework "UMCommon" -framework "UMDevice" -framework "WebKit" -framework "WechatOpenSDK" -weak_framework "Network" -weak_framework "UserNotifications"
|
||||
OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/AMap3DMap-NO-IDFA" "-F${PODS_CONFIGURATION_BUILD_DIR}/AMapFoundation-NO-IDFA" "-F${PODS_CONFIGURATION_BUILD_DIR}/AMapLocation-NO-IDFA" "-F${PODS_CONFIGURATION_BUILD_DIR}/AMapSearch-NO-IDFA" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQTextView" "-F${PODS_CONFIGURATION_BUILD_DIR}/JCore" "-F${PODS_CONFIGURATION_BUILD_DIR}/JPush" "-F${PODS_CONFIGURATION_BUILD_DIR}/UMAPM" "-F${PODS_CONFIGURATION_BUILD_DIR}/UMCommon" "-F${PODS_CONFIGURATION_BUILD_DIR}/UMDevice" "-F${PODS_CONFIGURATION_BUILD_DIR}/WechatOpenSDK-XCFramework"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView" "${PODS_ROOT}/JCore" "${PODS_ROOT}/JPush" "${PODS_ROOT}/UMAPM/UMAPM_2.0.7" "${PODS_ROOT}/UMCommon/UMCommon_7.5.11" "${PODS_ROOT}/UMDevice/UMDevice_3.6.0" "${PODS_ROOT}/WechatOpenSDK-XCFramework" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMAPM" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMCommon" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMDevice" "${PODS_XCFRAMEWORKS_BUILD_DIR}/WechatOpenSDK-XCFramework"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore/IQKeyboardCore.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification/IQKeyboardNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager/IQKeyboardReturnManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar/IQKeyboardToolbar.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager/IQKeyboardToolbarManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification/IQTextInputViewNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView/IQTextView.framework/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JPush/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JPush" "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
|
||||
OTHER_LDFLAGS = $(inherited) -ObjC -l"JCore" -l"JPush" -l"c++" -l"resolv" -l"sqlite3" -l"sqlite3.0" -l"z" -framework "CFNetwork" -framework "Combine" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreTelephony" -framework "Foundation" -framework "IQKeyboardCore" -framework "IQKeyboardManagerSwift" -framework "IQKeyboardNotification" -framework "IQKeyboardReturnManager" -framework "IQKeyboardToolbar" -framework "IQKeyboardToolbarManager" -framework "IQTextInputViewNotification" -framework "IQTextView" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "UMAPM" -framework "UMCommon" -framework "UMDevice" -framework "WebKit" -framework "WechatOpenSDK" -weak_framework "Network" -weak_framework "UserNotifications"
|
||||
OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification" "-F${PODS_CONFIGURATION_BUILD_DIR}/IQTextView" "-F${PODS_CONFIGURATION_BUILD_DIR}/JCore" "-F${PODS_CONFIGURATION_BUILD_DIR}/JPush" "-F${PODS_CONFIGURATION_BUILD_DIR}/UMAPM" "-F${PODS_CONFIGURATION_BUILD_DIR}/UMCommon" "-F${PODS_CONFIGURATION_BUILD_DIR}/UMDevice" "-F${PODS_CONFIGURATION_BUILD_DIR}/WechatOpenSDK-XCFramework"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
PODS_ROOT = ${SRCROOT}/Pods
|
||||
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
VALID_ARCHS = arm64 x86_64
|
||||
@@ -2,10 +2,10 @@ ARCHS = $(ARCHS_STANDARD)
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO
|
||||
EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView" "${PODS_ROOT}/AMap3DMap-NO-IDFA" "${PODS_ROOT}/AMapFoundation-NO-IDFA" "${PODS_ROOT}/AMapLocation-NO-IDFA" "${PODS_ROOT}/AMapSearch-NO-IDFA" "${PODS_ROOT}/UMAPM/UMAPM_2.0.7" "${PODS_ROOT}/UMCommon/UMCommon_7.5.11" "${PODS_ROOT}/UMDevice/UMDevice_3.6.0" "${PODS_ROOT}/WechatOpenSDK-XCFramework" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMAPM" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMCommon" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMDevice" "${PODS_XCFRAMEWORKS_BUILD_DIR}/WechatOpenSDK-XCFramework"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView" "${PODS_ROOT}/AMap3DMap-NO-IDFA" "${PODS_ROOT}/AMapFoundation-NO-IDFA" "${PODS_ROOT}/AMapLocation-NO-IDFA" "${PODS_ROOT}/AMapSearch-NO-IDFA" "${PODS_ROOT}/JCore" "${PODS_ROOT}/JPush" "${PODS_ROOT}/UMAPM/UMAPM_2.0.7" "${PODS_ROOT}/UMCommon/UMCommon_7.5.11" "${PODS_ROOT}/UMDevice/UMDevice_3.6.0" "${PODS_ROOT}/WechatOpenSDK-XCFramework" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMAPM" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMCommon" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMDevice" "${PODS_XCFRAMEWORKS_BUILD_DIR}/WechatOpenSDK-XCFramework"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore/IQKeyboardCore.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification/IQKeyboardNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager/IQKeyboardReturnManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar/IQKeyboardToolbar.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager/IQKeyboardToolbarManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification/IQTextInputViewNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView/IQTextView.framework/Headers"
|
||||
OTHER_LDFLAGS = $(inherited) -l"c++" -l"sqlite3" -l"sqlite3.0" -l"z" -framework "Combine" -framework "CoreGraphics" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "ExternalAccessory" -framework "GLKit" -framework "IQKeyboardCore" -framework "IQKeyboardManagerSwift" -framework "IQKeyboardNotification" -framework "IQKeyboardReturnManager" -framework "IQKeyboardToolbar" -framework "IQKeyboardToolbarManager" -framework "IQTextInputViewNotification" -framework "IQTextView" -framework "OpenGLES" -framework "QuartzCore" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "WebKit"
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore/IQKeyboardCore.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification/IQKeyboardNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager/IQKeyboardReturnManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar/IQKeyboardToolbar.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager/IQKeyboardToolbarManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification/IQTextInputViewNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView/IQTextView.framework/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JPush/Headers"
|
||||
OTHER_LDFLAGS = $(inherited) -l"c++" -l"resolv" -l"sqlite3" -l"sqlite3.0" -l"z" -framework "CFNetwork" -framework "Combine" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "ExternalAccessory" -framework "Foundation" -framework "GLKit" -framework "IQKeyboardCore" -framework "IQKeyboardManagerSwift" -framework "IQKeyboardNotification" -framework "IQKeyboardReturnManager" -framework "IQKeyboardToolbar" -framework "IQKeyboardToolbarManager" -framework "IQTextInputViewNotification" -framework "IQTextView" -framework "OpenGLES" -framework "QuartzCore" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "WebKit" -weak_framework "Network" -weak_framework "UserNotifications"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
|
||||
@@ -2,10 +2,10 @@ ARCHS = $(ARCHS_STANDARD)
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO
|
||||
EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView" "${PODS_ROOT}/AMap3DMap-NO-IDFA" "${PODS_ROOT}/AMapFoundation-NO-IDFA" "${PODS_ROOT}/AMapLocation-NO-IDFA" "${PODS_ROOT}/AMapSearch-NO-IDFA" "${PODS_ROOT}/UMAPM/UMAPM_2.0.7" "${PODS_ROOT}/UMCommon/UMCommon_7.5.11" "${PODS_ROOT}/UMDevice/UMDevice_3.6.0" "${PODS_ROOT}/WechatOpenSDK-XCFramework" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMAPM" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMCommon" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMDevice" "${PODS_XCFRAMEWORKS_BUILD_DIR}/WechatOpenSDK-XCFramework"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView" "${PODS_ROOT}/AMap3DMap-NO-IDFA" "${PODS_ROOT}/AMapFoundation-NO-IDFA" "${PODS_ROOT}/AMapLocation-NO-IDFA" "${PODS_ROOT}/AMapSearch-NO-IDFA" "${PODS_ROOT}/JCore" "${PODS_ROOT}/JPush" "${PODS_ROOT}/UMAPM/UMAPM_2.0.7" "${PODS_ROOT}/UMCommon/UMCommon_7.5.11" "${PODS_ROOT}/UMDevice/UMDevice_3.6.0" "${PODS_ROOT}/WechatOpenSDK-XCFramework" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMAPM" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMCommon" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMDevice" "${PODS_XCFRAMEWORKS_BUILD_DIR}/WechatOpenSDK-XCFramework"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore/IQKeyboardCore.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification/IQKeyboardNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager/IQKeyboardReturnManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar/IQKeyboardToolbar.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager/IQKeyboardToolbarManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification/IQTextInputViewNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView/IQTextView.framework/Headers"
|
||||
OTHER_LDFLAGS = $(inherited) -l"c++" -l"sqlite3" -l"sqlite3.0" -l"z" -framework "Combine" -framework "CoreGraphics" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "ExternalAccessory" -framework "GLKit" -framework "IQKeyboardCore" -framework "IQKeyboardManagerSwift" -framework "IQKeyboardNotification" -framework "IQKeyboardReturnManager" -framework "IQKeyboardToolbar" -framework "IQKeyboardToolbarManager" -framework "IQTextInputViewNotification" -framework "IQTextView" -framework "OpenGLES" -framework "QuartzCore" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "WebKit"
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore/IQKeyboardCore.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification/IQKeyboardNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager/IQKeyboardReturnManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar/IQKeyboardToolbar.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager/IQKeyboardToolbarManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification/IQTextInputViewNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView/IQTextView.framework/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JPush/Headers"
|
||||
OTHER_LDFLAGS = $(inherited) -l"c++" -l"resolv" -l"sqlite3" -l"sqlite3.0" -l"z" -framework "CFNetwork" -framework "Combine" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "ExternalAccessory" -framework "Foundation" -framework "GLKit" -framework "IQKeyboardCore" -framework "IQKeyboardManagerSwift" -framework "IQKeyboardNotification" -framework "IQKeyboardReturnManager" -framework "IQKeyboardToolbar" -framework "IQKeyboardToolbarManager" -framework "IQTextInputViewNotification" -framework "IQTextView" -framework "OpenGLES" -framework "QuartzCore" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "WebKit" -weak_framework "Network" -weak_framework "UserNotifications"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
ARCHS = $(ARCHS_STANDARD)
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO
|
||||
EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView" "${PODS_ROOT}/JCore" "${PODS_ROOT}/JPush" "${PODS_ROOT}/UMAPM/UMAPM_2.0.7" "${PODS_ROOT}/UMCommon/UMCommon_7.5.11" "${PODS_ROOT}/UMDevice/UMDevice_3.6.0" "${PODS_ROOT}/WechatOpenSDK-XCFramework" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMAPM" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMCommon" "${PODS_XCFRAMEWORKS_BUILD_DIR}/UMDevice" "${PODS_XCFRAMEWORKS_BUILD_DIR}/WechatOpenSDK-XCFramework"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardCore/IQKeyboardCore.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardNotification/IQKeyboardNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardReturnManager/IQKeyboardReturnManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbar/IQKeyboardToolbar.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardToolbarManager/IQKeyboardToolbarManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextInputViewNotification/IQTextInputViewNotification.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQTextView/IQTextView.framework/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JCore/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/JPush/Headers"
|
||||
OTHER_LDFLAGS = $(inherited) -l"c++" -l"resolv" -l"sqlite3" -l"sqlite3.0" -l"z" -framework "CFNetwork" -framework "Combine" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreTelephony" -framework "Foundation" -framework "IQKeyboardCore" -framework "IQKeyboardManagerSwift" -framework "IQKeyboardNotification" -framework "IQKeyboardReturnManager" -framework "IQKeyboardToolbar" -framework "IQKeyboardToolbarManager" -framework "IQTextInputViewNotification" -framework "IQTextView" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "WebKit" -weak_framework "Network" -weak_framework "UserNotifications"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
PODS_ROOT = ${SRCROOT}/Pods
|
||||
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
VALID_ARCHS = arm64 x86_64
|
||||
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 926 KiB |
|
After Width: | Height: | Size: 893 KiB |
|
After Width: | Height: | Size: 893 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
@@ -0,0 +1,484 @@
|
||||
# 后端消息推送格式
|
||||
|
||||
## 1. 文档说明
|
||||
|
||||
- 本文整理后端经极光推送发送到 Android 和 iOS 的通知消息格式。
|
||||
- 当前内容基于截至 2026-07-23 的 Android、iOS 实际收包日志、两端客户端现有解析逻辑,以及后端开发人员提供的 `PushMsg` 类型定义。
|
||||
- 后端当前定义了 `1`~`13`、`100`、`999` 共 15 个业务消息类型;目前已取得 `type = 1`(付款成功)、`type = 2`(退款成功)、`type = 6`(扫码成功)和 `type = 13`(新获客线索)的收包样本。
|
||||
- 本文描述的是推送协议,不是普通 HTTP 接口响应格式。
|
||||
|
||||
## 2. 统一业务结构
|
||||
|
||||
极光在 Android 和 iOS 上交付的原始结构不同,但当前消息都可以归一化为以下业务结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "通知标题",
|
||||
"content": "通知正文",
|
||||
"msg_id": 1858,
|
||||
"type": 6,
|
||||
"data": {
|
||||
"order_type": 14
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.1 公共业务字段
|
||||
|
||||
| 字段 | 当前类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `title` | string | 通知标题。Android 为 `notificationTitle`,iOS 为 `aps.alert.title`。 |
|
||||
| `content` | string | 通知正文。Android 为 `notificationContent`,iOS 为 `aps.alert.body`。 |
|
||||
| `msg_id` | int | 后端业务消息 ID。它与极光消息 ID 不是同一个字段。 |
|
||||
| `type` | int | 业务消息类型,是客户端业务分发的主要依据。 |
|
||||
| `data` | object | 随消息类型变化的业务数据。 |
|
||||
|
||||
> “当前类型”表示现有样本中观察到的 JSON 类型。后端应保持字段类型稳定,避免同一个字段在不同推送中交替使用数字、字符串或 `null`。
|
||||
|
||||
## 3. Android 收包格式
|
||||
|
||||
Android 通过极光 `JPushMessageReceiver.onNotifyMessageArrived` 收到通知。标题、正文和附加字段分别由 SDK 提供,其中 `extras` 是 JSON 字符串:
|
||||
|
||||
```text
|
||||
[onNotifyMessageArrived] msgId=<极光消息ID>, title=<标题>, content=<正文>, extras=<业务JSON字符串>
|
||||
```
|
||||
|
||||
`extras` 的当前结构为:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {},
|
||||
"msg_id": 0,
|
||||
"type": 0
|
||||
}
|
||||
```
|
||||
|
||||
### 3.1 扫码成功原始日志
|
||||
|
||||
```text
|
||||
[onNotifyMessageArrived] msgId=18103321643292031, title=扫码成功通知, content=扫码成功, extras={"data":{"order_type":14},"msg_id":1852,"type":6}
|
||||
```
|
||||
|
||||
对应的 `extras`:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"order_type": 14
|
||||
},
|
||||
"msg_id": 1852,
|
||||
"type": 6
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 付款成功原始日志
|
||||
|
||||
```text
|
||||
[onNotifyMessageArrived] msgId=18103321644290274, title=付款成功通知, content=您的订单 #260722128005 已支付成功,支付金额 ¥0.01, extras={"data":{"order_amount":"0.01","order_number":"260722128005","order_type":14,"pay_time":1784711036,"remark":""},"msg_id":1853,"type":1}
|
||||
```
|
||||
|
||||
对应的 `extras`:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"order_amount": "0.01",
|
||||
"order_number": "260722128005",
|
||||
"order_type": 14,
|
||||
"pay_time": 1784711036,
|
||||
"remark": ""
|
||||
},
|
||||
"msg_id": 1853,
|
||||
"type": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 退款成功原始日志
|
||||
|
||||
```text
|
||||
[onNotifyMessageArrived] msgId=18103321800527159, title=退款成功通知, content=您的订单 #260722128002 已成功退款,退款金额 ¥0.01, extras={"data":{},"msg_id":1867,"type":2}
|
||||
```
|
||||
|
||||
对应的 `extras`:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {},
|
||||
"msg_id": 1867,
|
||||
"type": 2
|
||||
}
|
||||
```
|
||||
|
||||
## 4. iOS 收包格式
|
||||
|
||||
iOS 通过 APNs/极光收到 `userInfo` 字典。与 Android 相比:
|
||||
|
||||
- 标题和正文位于 `aps.alert`。
|
||||
- Android `extras` 中的 `msg_id`、`type`、`data` 在 iOS 中位于 payload 顶层。
|
||||
- `_j_*` 为极光传输层内部字段,不应作为业务分发依据。
|
||||
|
||||
通用结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"_j_business": 1,
|
||||
"_j_data": "{\"data_msgtype\":1,\"push_type\":8,\"is_vip\":0}",
|
||||
"_j_msgid": 18103321726881995,
|
||||
"_j_uid": 86399144805,
|
||||
"aps": {
|
||||
"alert": {
|
||||
"body": "通知正文",
|
||||
"title": "通知标题"
|
||||
},
|
||||
"sound": "default"
|
||||
},
|
||||
"data": {},
|
||||
"msg_id": 0,
|
||||
"type": 0
|
||||
}
|
||||
```
|
||||
|
||||
> 原始日志中该内部字段显示为 `*j\_data*`。本文按极光其他内部字段的命名方式记为 `_j_data`;后续联调时应以设备收到的真实字典 key 为准。该字段目前不参与业务解析。
|
||||
|
||||
### 4.1 扫码成功 payload
|
||||
|
||||
```json
|
||||
{
|
||||
"_j_business": 1,
|
||||
"_j_data": "{\"data_msgtype\":1,\"push_type\":8,\"is_vip\":0}",
|
||||
"_j_msgid": 18103321726881995,
|
||||
"_j_uid": 86399144805,
|
||||
"aps": {
|
||||
"alert": {
|
||||
"body": "扫码成功",
|
||||
"title": "扫码成功通知"
|
||||
},
|
||||
"sound": "default"
|
||||
},
|
||||
"data": {
|
||||
"order_type": 14
|
||||
},
|
||||
"msg_id": 1858,
|
||||
"type": 6
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 付款成功 payload
|
||||
|
||||
```json
|
||||
{
|
||||
"_j_business": 1,
|
||||
"_j_data": "{\"data_msgtype\":1,\"push_type\":8,\"is_vip\":0}",
|
||||
"_j_msgid": 18103321720587147,
|
||||
"_j_uid": 86399144805,
|
||||
"aps": {
|
||||
"alert": {
|
||||
"body": "您的订单 #260722128006 已支付成功,支付金额 ¥0.01",
|
||||
"title": "付款成功通知"
|
||||
},
|
||||
"sound": "default"
|
||||
},
|
||||
"data": {
|
||||
"order_amount": "0.01",
|
||||
"order_number": "260722128006",
|
||||
"order_type": 14,
|
||||
"pay_time": 1784712671,
|
||||
"remark": ""
|
||||
},
|
||||
"msg_id": 1856,
|
||||
"type": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 退款成功 payload
|
||||
|
||||
```json
|
||||
{
|
||||
"_j_business": 1,
|
||||
"_j_data": "{\"data_msgtype\":1,\"push_type\":8,\"is_vip\":0}",
|
||||
"_j_msgid": 18103321792603316,
|
||||
"_j_uid": 86399144805,
|
||||
"aps": {
|
||||
"alert": {
|
||||
"body": "您的订单 #260722128005 已成功退款,退款金额 ¥0.01",
|
||||
"title": "退款成功通知"
|
||||
},
|
||||
"sound": "default"
|
||||
},
|
||||
"data": {},
|
||||
"msg_id": 1862,
|
||||
"type": 2
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 新获客线索 payload
|
||||
|
||||
```json
|
||||
{
|
||||
"_j_business": 1,
|
||||
"_j_data": "{\"data_msgtype\":1,\"push_type\":8,\"is_vip\":0}",
|
||||
"_j_msgid": 18103323696468714,
|
||||
"_j_uid": 86399144805,
|
||||
"aps": {
|
||||
"alert": {
|
||||
"body": "获客员哦啦啦提交了一条新线索,请及时查看并跟进",
|
||||
"title": "新获客线索通知"
|
||||
},
|
||||
"sound": "default"
|
||||
},
|
||||
"data": {
|
||||
"lead_id": 75,
|
||||
"sale_user_id": 6,
|
||||
"saler_name": "哦啦啦"
|
||||
},
|
||||
"msg_id": 1869,
|
||||
"type": 13
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Android 与 iOS 字段对应关系
|
||||
|
||||
| 语义 | Android | iOS | 是否用于业务 |
|
||||
| --- | --- | --- | --- |
|
||||
| 极光消息 ID | SDK `message.msgId` | `_j_msgid` | 否,仅用于推送链路日志与排查 |
|
||||
| 通知标题 | SDK `message.notificationTitle` | `aps.alert.title` | 是,用于展示 |
|
||||
| 通知正文 | SDK `message.notificationContent` | `aps.alert.body` | 是,用于展示 |
|
||||
| 后端业务消息 ID | `extras.msg_id` | 顶层 `msg_id` | 是 |
|
||||
| 业务消息类型 | `extras.type` | 顶层 `type` | 是,作为业务分发主键 |
|
||||
| 业务数据 | `extras.data` | 顶层 `data` | 是,结构由 `type` 决定 |
|
||||
| 极光业务标记 | SDK 内部处理 | `_j_business` | 否 |
|
||||
| 极光附加元数据 | SDK 内部处理 | `_j_data` | 否 |
|
||||
| 极光用户 ID | SDK 内部处理 | `_j_uid` | 否 |
|
||||
| APNs 展示配置 | 不适用 | `aps` | 仅 iOS 系统使用 |
|
||||
|
||||
### 5.1 两种消息 ID 不可混用
|
||||
|
||||
- `msgId` / `_j_msgid`:极光生成的传输层消息 ID,当前样本为 17 位数字。
|
||||
- `msg_id`:后端生成的业务消息 ID,当前样本为 `1852`、`1853`、`1856`、`1858`。
|
||||
- 客户端日志、去重或上报时必须明确需要哪一种 ID,不能仅用名称相近而互相替代。
|
||||
- 极光消息 ID 已超过 JavaScript 可安全表示的整数范围;若需要经过 JavaScript、WebView 或其他只能安全处理 53 位整数的链路,应按字符串传递。
|
||||
|
||||
## 6. 消息类型清单
|
||||
|
||||
### 6.1 后端 `PushMsg` 完整类型定义
|
||||
|
||||
定义位置:后端 `PushMsg.php` 第 49 行附近。
|
||||
|
||||
| `type` | 消息类型 | 后端备注 | 当前收包样本 |
|
||||
| ---: | --- | --- | --- |
|
||||
| `1` | 付款成功通知 | 上文实际示例即为此类型 | Android、iOS 均已取得 |
|
||||
| `2` | 退款成功通知 | — | Android、iOS 均已取得 |
|
||||
| `3` | 下单成功通知 | App 无需推送,当前未使用 | 暂无 |
|
||||
| `4` | 签到成功通知 | — | 暂无 |
|
||||
| `5` | 付尾款成功通知 | — | 暂无 |
|
||||
| `6` | 扫码成功通知 | — | Android、iOS 均已取得 |
|
||||
| `7` | 付定金成功通知 | — | 暂无 |
|
||||
| `8` | 订单核销成功通知 | — | 暂无 |
|
||||
| `9` | 剪辑完成通知 | — | 暂无 |
|
||||
| `10` | 修图完成通知 | — | 暂无 |
|
||||
| `11` | 跟拍签到通知 | — | 暂无 |
|
||||
| `12` | 剪辑师新任务通知 | 类型名称映射中漏配 | 暂无 |
|
||||
| `13` | 新获客线索通知 | 获客员提交新线索 | iOS 已取得,Android 暂无 |
|
||||
| `100` | 用户登出通知 | 账号在其他设备登录 | 暂无 |
|
||||
| `999` | 系统消息 | 当前未使用 | 暂无 |
|
||||
|
||||
后端特别说明:
|
||||
|
||||
- `type` 表示业务消息类型,不代表推送渠道。
|
||||
- 推送渠道的取值另有定义:`1` 为极光推送,`2` 为微信小程序,`999` 为不推送。
|
||||
- `type = 12` 虽然已经定义,但尚未加入后端 `MAP_TYPE_NAMES`。后端调用 `getTypeName(12)` 时会返回“未知消息类型”,需要后端补充映射。
|
||||
- “暂无收包样本”只表示当前文档还没有该类型的完整 payload,不能据此认为该类型未启用。收到后应继续补充其标题、正文、`data` 字段和点击行为。
|
||||
|
||||
### 6.2 `type = 1`:付款成功
|
||||
|
||||
| 项目 | 当前值 |
|
||||
| --- | --- |
|
||||
| 标题示例 | `付款成功通知` |
|
||||
| 正文示例 | `您的订单 #260722128006 已支付成功,支付金额 ¥0.01` |
|
||||
| 客户端点击行为 | Android、iOS 均进入收款记录页 |
|
||||
|
||||
`data` 字段:
|
||||
|
||||
| 字段 | 类型 | 当前样本 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `order_amount` | string | `"0.01"` | 支付金额。使用十进制字符串,避免浮点精度问题。 |
|
||||
| `order_number` | string | `"260722128006"` | 业务订单号。 |
|
||||
| `order_type` | int | `14` | 订单类型;当前值表示“摄影师跟拍线下扫码”。 |
|
||||
| `pay_time` | int | `1784712671` | 支付时间。当前样本为 Unix 秒级时间戳;Android 现有代码同时兼容秒和毫秒。 |
|
||||
| `remark` | string | `""` | 付款备注;没有备注时当前返回空字符串。 |
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": 1,
|
||||
"msg_id": 1856,
|
||||
"data": {
|
||||
"order_amount": "0.01",
|
||||
"order_number": "260722128006",
|
||||
"order_type": 14,
|
||||
"pay_time": 1784712671,
|
||||
"remark": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 `type = 2`:退款成功
|
||||
|
||||
| 项目 | 当前值 |
|
||||
| --- | --- |
|
||||
| 标题示例 | `退款成功通知` |
|
||||
| 正文示例 | `您的订单 #260722128005 已成功退款,退款金额 ¥0.01` |
|
||||
| 客户端点击行为 | Android、iOS 均进入消息中心 |
|
||||
|
||||
`data` 字段:
|
||||
|
||||
| 字段 | 类型 | 当前样本 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| — | object | `{}` | 当前 Android、iOS 样本均为空对象。 |
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": 2,
|
||||
"msg_id": 1862,
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
> 当前订单号和退款金额只存在于展示正文中,没有以结构化字段放入 `data`。客户端如果需要可靠地刷新指定订单、展示退款详情或执行金额计算,应由后端在 `data` 中提供字段,不应解析自然语言正文。
|
||||
|
||||
### 6.4 `type = 6`:扫码成功
|
||||
|
||||
| 项目 | 当前值 |
|
||||
| --- | --- |
|
||||
| 标题示例 | `扫码成功通知` |
|
||||
| 正文示例 | `扫码成功` |
|
||||
| 客户端点击行为 | Android、iOS 均进入收款详情页 |
|
||||
|
||||
`data` 字段:
|
||||
|
||||
| 字段 | 类型 | 当前样本 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `order_type` | int | `14` | 订单类型;`14` 在 Android 现有代码中表示“摄影师跟拍线下扫码”。 |
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": 6,
|
||||
"msg_id": 1858,
|
||||
"data": {
|
||||
"order_type": 14
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.5 `type = 13`:新获客线索
|
||||
|
||||
| 项目 | 当前值 |
|
||||
| --- | --- |
|
||||
| 标题示例 | `新获客线索通知` |
|
||||
| 正文示例 | `获客员哦啦啦提交了一条新线索,请及时查看并跟进` |
|
||||
| 客户端点击行为 | Android、iOS 均进入消息中心 |
|
||||
|
||||
`data` 字段:
|
||||
|
||||
| 字段 | 类型 | 当前样本 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `lead_id` | int | `75` | 新增获客线索的唯一 ID。 |
|
||||
| `sale_user_id` | int | `6` | 提交线索的获客员 ID。 |
|
||||
| `saler_name` | string | `"哦啦啦"` | 获客员名称,与通知正文中的名称一致。 |
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": 13,
|
||||
"msg_id": 1869,
|
||||
"data": {
|
||||
"lead_id": 75,
|
||||
"sale_user_id": 6,
|
||||
"saler_name": "哦啦啦"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 当前点击路由只根据 `type` 判断,`type = 13` 属于默认分支,因此进入消息中心;`data` 中的线索字段不会改变跳转页面。
|
||||
|
||||
## 7. 客户端解析约定
|
||||
|
||||
1. Android、iOS 的通知点击路由只根据业务字段 `type` 判断,不读取标题、正文、`data`、`route`、`uri`、`action` 或极光内部字段决定页面。
|
||||
2. `type = 1`:进入收款记录页。
|
||||
3. `type = 6`:进入收款详情页。
|
||||
4. 其他任意 `type`、缺少 `type`、类型无法解析或 payload 损坏:进入消息中心。
|
||||
5. Android 从 `notificationExtras` 或厂商通道包装字段中提取 `type`;iOS 优先读取 payload 顶层 `type`,并兼容常见推送包装层中的 `type`。
|
||||
6. `data` 仍可按消息类型用于页面内容刷新,但不参与通知点击路由。新增字段应保持向后兼容,客户端应忽略当前版本不认识的额外字段。
|
||||
7. 金额继续使用字符串;时间戳建议统一为 Unix 秒级整数,并在协议中固定单位。
|
||||
8. `remark` 等可空业务字段建议固定返回空字符串或明确约定 `null`,不要在多种表示之间切换。
|
||||
|
||||
## 8. 新增消息类型记录模板
|
||||
|
||||
后续收到新类型时,复制以下内容追加到“消息类型清单”:
|
||||
|
||||
````markdown
|
||||
### 6.x `type = <类型值>`:<消息名称>
|
||||
|
||||
| 项目 | 当前值 |
|
||||
| --- | --- |
|
||||
| 首次发现日期 | `YYYY-MM-DD` |
|
||||
| 标题示例 | `<title>` |
|
||||
| 正文示例 | `<content>` |
|
||||
| 客户端业务行为 | `<展示、刷新或跳转行为>` |
|
||||
|
||||
`data` 字段:
|
||||
|
||||
| 字段 | 类型 | 必有 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `<field>` | `<type>` | `<是/否/待确认>` | `<说明>` |
|
||||
|
||||
Android `extras` 示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {},
|
||||
"msg_id": 0,
|
||||
"type": 0
|
||||
}
|
||||
```
|
||||
|
||||
iOS 业务字段示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {},
|
||||
"msg_id": 0,
|
||||
"type": 0
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
记录新类型时至少保留以下信息:
|
||||
|
||||
- Android 和 iOS 各一份原始收包日志。
|
||||
- `type`、`msg_id` 和完整 `data`。
|
||||
- 标题、正文以及消息点击后的期望页面。
|
||||
- 每个字段的 JSON 类型、是否必有、空值表示方式和时间/金额单位。
|
||||
- 同一业务事件在两端的字段是否一致。
|
||||
|
||||
## 9. 待后端确认项
|
||||
|
||||
| 项目 | 当前观察 | 需要确认 |
|
||||
| --- | --- | --- |
|
||||
| `msg_id` | 与极光消息 ID 不同 | 是否对应消息中心记录 ID,以及是否用于已读/送达状态上报 |
|
||||
| `data` | `type = 1`、`2`、`6`、`13` 的样本中都存在;`type = 2` 为 `{}` | 所有业务推送是否保证存在;无业务字段时是否统一返回 `{}` |
|
||||
| 退款成功的 `data` | 当前为空对象,订单号和退款金额只在正文中 | 是否补充 `order_number`、`refund_amount`、`refund_time`、`order_type` 等结构化字段 |
|
||||
| 其他类型的 `data` | 后端已定义消息名称,但尚未提供 payload | 分别确认 `type = 3`~`5`、`7`~`12`、`100`、`999` 的字段结构、必填项及点击行为 |
|
||||
| `pay_time` | 当前为秒级时间戳 | 后端是否统一保证使用 Unix 秒,且时区语义固定为 UTC 时间戳 |
|
||||
| `remark` | 无备注时为空字符串 | 是否可能返回 `null` 或省略 |
|
||||
| `order_type` | 当前为 `14` | 完整枚举和值含义由哪份接口文档维护 |
|
||||
| 标题与正文 | 当前均存在 | 是否为所有通知消息的必有字段 |
|
||||
| 推送渠道字段 | 后端已给出渠道取值 `1`、`2`、`999` | payload 或发送接口中对应字段的准确名称 |
|
||||
| `type = 12` | `MAP_TYPE_NAMES` 中漏配 | 后端补充映射,避免 `getTypeName(12)` 返回“未知消息类型” |
|
||||
| 新增 `type` | 当前定义维护在 `PushMsg.php` | 后端新增类型前是否可同步类型值、字段结构和客户端行为 |
|
||||
@@ -0,0 +1,204 @@
|
||||
# 消息中心未读数量与全部已读接口需求
|
||||
|
||||
## 1. 需求背景
|
||||
|
||||
iOS App 需要实现以下功能:
|
||||
|
||||
1. 首页展示消息中心入口,有未读消息时显示红点。
|
||||
2. 获取最新未读消息数量后,同步更新 App 桌面图标右上角的角标数量。
|
||||
3. 用户点击某条未读消息并成功标记已读后,立即更新最新未读数量和桌面角标。
|
||||
4. 消息中心导航栏右上角提供“全部已读”按钮。
|
||||
5. 全部标记已读成功后,更新首页红点,并将桌面角标更新为服务端返回的最新未读数量。
|
||||
|
||||
## 2. 当前已有接口
|
||||
|
||||
### 2.1 获取消息列表
|
||||
|
||||
```http
|
||||
GET /api/app/msg/list
|
||||
```
|
||||
|
||||
当前参数:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `last_id` | int | 否 | 分页游标,首次请求传 `0` |
|
||||
| `limit` | int | 否 | 每页数量 |
|
||||
| `unread` | int | 否 | `0` 获取全部消息,`1` 只获取未读消息 |
|
||||
|
||||
该接口仅返回分页消息,没有返回未读消息总数。客户端若通过分页遍历全部未读消息来统计数量,请求次数和数据流量都会随着未读消息数量增长,因此不适合作为桌面角标的常规数据来源。
|
||||
|
||||
### 2.2 标记单条消息已读
|
||||
|
||||
```http
|
||||
POST /api/app/msg/read?id={message_id}
|
||||
```
|
||||
|
||||
当前接口可以标记单条消息已读,但成功响应没有返回操作后的最新未读数量。
|
||||
|
||||
## 3. 需要新增或调整的接口
|
||||
|
||||
### 3.1 获取未读消息总数
|
||||
|
||||
建议新增:
|
||||
|
||||
```http
|
||||
GET /api/app/msg/unread-count
|
||||
```
|
||||
|
||||
#### 请求说明
|
||||
|
||||
- 不需要业务请求参数。
|
||||
- 根据当前登录 Token 识别用户及业务账号。
|
||||
- 返回当前账号下尚未读取的消息总数。
|
||||
|
||||
#### 成功响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 100000,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"unread_count": 12
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 字段说明
|
||||
|
||||
| 字段 | 类型 | 必有 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `unread_count` | int | 是 | 当前账号的最新未读消息数量,最小值为 `0` |
|
||||
|
||||
### 3.2 全部标记为已读
|
||||
|
||||
建议新增:
|
||||
|
||||
```http
|
||||
POST /api/app/msg/read-all
|
||||
```
|
||||
|
||||
#### 请求说明
|
||||
|
||||
- 不需要业务请求参数和请求体。
|
||||
- 根据当前登录 Token 识别用户及业务账号。
|
||||
- 将当前账号下请求执行时已经存在的所有未读消息标记为已读。
|
||||
- 接口需要支持幂等调用;没有未读消息时仍返回成功。
|
||||
|
||||
#### 成功响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 100000,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"updated_count": 12,
|
||||
"unread_count": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 字段说明
|
||||
|
||||
| 字段 | 类型 | 必有 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `updated_count` | int | 是 | 本次实际被更新为已读的消息数量 |
|
||||
| `unread_count` | int | 是 | 操作完成后的最新未读消息数量 |
|
||||
|
||||
> 客户端应以响应中的 `unread_count` 为准,不应直接假定其一定为 `0`。例如“全部已读”执行期间如果有新消息到达,操作完成后仍可能存在新的未读消息。
|
||||
|
||||
### 3.3 调整单条已读接口响应
|
||||
|
||||
保留现有接口:
|
||||
|
||||
```http
|
||||
POST /api/app/msg/read?id={message_id}
|
||||
```
|
||||
|
||||
建议成功响应增加最新未读数量:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 100000,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"unread_count": 11
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 字段说明
|
||||
|
||||
| 字段 | 类型 | 必有 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `unread_count` | int | 是 | 单条消息标记已读后的最新未读消息数量 |
|
||||
|
||||
#### 幂等要求
|
||||
|
||||
- 对已经处于已读状态的消息重复调用时,接口仍返回成功和最新 `unread_count`。
|
||||
- 消息不存在或不属于当前账号时,返回明确的业务错误,不能修改其他账号的消息。
|
||||
|
||||
## 4. 统一约定
|
||||
|
||||
### 4.1 账号范围
|
||||
|
||||
- 所有接口必须根据登录 Token 限定当前用户及当前业务账号的数据范围。
|
||||
- 景区账号、门店账号等不同业务账号之间的未读数量不能互相污染。
|
||||
- 切换账号后,客户端会重新调用未读数量接口。
|
||||
|
||||
### 4.2 数值约束
|
||||
|
||||
- `unread_count` 和 `updated_count` 必须返回 JSON 整数。
|
||||
- 数值不得为负数。
|
||||
- 即使数量为 `0`,字段也必须存在,不能返回 `null`、空字符串或省略字段。
|
||||
|
||||
### 4.3 一致性与并发
|
||||
|
||||
- 单条已读或全部已读操作及其响应中的 `unread_count` 应基于同一账号范围。
|
||||
- `read-all` 应尽量在事务内完成批量更新和剩余未读数统计。
|
||||
- 如果操作期间有新消息写入,响应应返回操作完成时数据库中的真实 `unread_count`。
|
||||
- 客户端始终以最近一次成功接口响应中的 `unread_count` 更新首页红点和桌面角标。
|
||||
|
||||
### 4.4 错误响应
|
||||
|
||||
沿用现有统一业务响应结构,例如:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 100001,
|
||||
"msg": "消息不存在或无权操作",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
建议至少区分以下情况:
|
||||
|
||||
- Token 无效或登录已过期。
|
||||
- 单条消息不存在。
|
||||
- 单条消息不属于当前账号。
|
||||
- 数据库更新失败。
|
||||
|
||||
## 5. 客户端调用时机
|
||||
|
||||
| 场景 | 调用接口 | 客户端处理 |
|
||||
| --- | --- | --- |
|
||||
| 登录后首次进入首页 | `GET /msg/unread-count` | 更新首页红点和桌面角标 |
|
||||
| App 回到前台 | `GET /msg/unread-count` | 校准最新未读数量 |
|
||||
| 前台收到新消息推送 | `GET /msg/unread-count` | 更新首页红点和桌面角标 |
|
||||
| 点击一条未读消息 | `POST /msg/read` | 使用返回的 `unread_count` 更新红点和桌面角标 |
|
||||
| 点击“全部已读” | `POST /msg/read-all` | 刷新消息列表,使用返回的 `unread_count` 更新红点和桌面角标 |
|
||||
| 切换业务账号 | `GET /msg/unread-count` | 使用新账号的数量覆盖旧账号角标 |
|
||||
| 退出登录 | 无 | 客户端将桌面角标清零 |
|
||||
|
||||
## 6. 验收标准
|
||||
|
||||
1. 未读数量为 `0` 时,接口稳定返回 `{"unread_count": 0}`。
|
||||
2. 存在多页未读消息时,未读数量接口返回完整总数,而不是当前页数量。
|
||||
3. 单条未读消息标记成功后,返回的 `unread_count` 与数据库实际剩余未读数一致。
|
||||
4. 对同一消息重复调用单条已读接口不会重复扣减未读数量。
|
||||
5. 全部已读接口可一次处理当前账号的所有未读消息,并返回实际更新数量。
|
||||
6. 重复调用全部已读接口仍成功,返回 `updated_count = 0`。
|
||||
7. 不同业务账号的未读数量相互隔离。
|
||||
8. 并发到达新消息时,响应中的 `unread_count` 能反映操作完成时的真实剩余数量。
|
||||
9. 所有接口继续使用项目现有统一鉴权、业务状态码和响应信封格式。
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
93FF17C52FFB84950033C304 /* suixinkan.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = suixinkan.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A1B2C3D02FFB84950033C305 /* suixinkanTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = suixinkanTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C69C08A144C2D5D4A448B7D2 /* Pods_suixinkan.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_suixinkan.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DC152A95F1705B826BE8A56A /* Pods-suixinkanTests.simulatordebug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-suixinkanTests.simulatordebug.xcconfig"; path = "Target Support Files/Pods-suixinkanTests/Pods-suixinkanTests.simulatordebug.xcconfig"; sourceTree = "<group>"; };
|
||||
ECE6D74459339B5075CF14AD /* Pods-suixinkan.simulatordebug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-suixinkan.simulatordebug.xcconfig"; path = "Target Support Files/Pods-suixinkan/Pods-suixinkan.simulatordebug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
@@ -111,6 +113,8 @@
|
||||
1060D5E389506965D83EA1CC /* Pods-suixinkan.release.xcconfig */,
|
||||
83445E7B4CCCF42C6FD481D3 /* Pods-suixinkanTests.debug.xcconfig */,
|
||||
6CB3E1DA4E97AA4961B25733 /* Pods-suixinkanTests.release.xcconfig */,
|
||||
ECE6D74459339B5075CF14AD /* Pods-suixinkan.simulatordebug.xcconfig */,
|
||||
DC152A95F1705B826BE8A56A /* Pods-suixinkanTests.simulatordebug.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
@@ -208,6 +212,11 @@
|
||||
TargetAttributes = {
|
||||
93FF17C42FFB84950033C304 = {
|
||||
CreatedOnToolsVersion = 26.6;
|
||||
SystemCapabilities = {
|
||||
com.apple.Push = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
A1B2C3D32FFB84950033C305 = {
|
||||
CreatedOnToolsVersion = 26.6;
|
||||
@@ -422,11 +431,12 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 5BBB56C2281879245220A9F3 /* Pods-suixinkan.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
APS_ENVIRONMENT = development;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = suixinkan/suixinkan.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1010402;
|
||||
CURRENT_PROJECT_VERSION = 1020101;
|
||||
DEVELOPMENT_TEAM = 56GVN5RNVN;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
"FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = (
|
||||
@@ -450,7 +460,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.1.4;
|
||||
MARKETING_VERSION = 1.2.1;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -487,11 +497,12 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 1060D5E389506965D83EA1CC /* Pods-suixinkan.release.xcconfig */;
|
||||
buildSettings = {
|
||||
APS_ENVIRONMENT = production;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = suixinkan/suixinkan.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1010402;
|
||||
CURRENT_PROJECT_VERSION = 1020101;
|
||||
DEVELOPMENT_TEAM = 56GVN5RNVN;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
"FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = (
|
||||
@@ -515,7 +526,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.1.4;
|
||||
MARKETING_VERSION = 1.2.1;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -721,6 +732,150 @@
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
D1A2B3C42FFB84950033C401 /* SimulatorDebug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = ECE6D74459339B5075CF14AD /* Pods-suixinkan.simulatordebug.xcconfig */;
|
||||
buildSettings = {
|
||||
APS_ENVIRONMENT = development;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = suixinkan/suixinkan.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1020101;
|
||||
DEVELOPMENT_TEAM = 56GVN5RNVN;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = suixinkan/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "随心瞰商家版";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "需要访问相机以拍摄任务素材、扫描订单核销二维码,并连接有线相机进行旅拍相册传输";
|
||||
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "需要获取您的位置信息,用于位置上报、景区距离排序及多点位核销打卡";
|
||||
INFOPLIST_KEY_NSMicrophoneUsageDescription = "需要访问麦克风以在拍摄素材视频时录制声音";
|
||||
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "需要保存收款二维码、排队打卡点小程序码和云盘图片视频到相册";
|
||||
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "需要访问相册以选择头像、身份证、银行卡照片和云盘上传素材";
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2.1;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.yuanzhixiang.suixinkan;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphonesimulator;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SUPPORTED_PLATFORMS = iphonesimulator;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "suixinkan/suixinkan-Bridging-Header.h";
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
name = SimulatorDebug;
|
||||
};
|
||||
D1A2B3C52FFB84950033C402 /* SimulatorDebug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 56GVN5RNVN;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 26.5;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphonesimulator;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = SimulatorDebug;
|
||||
};
|
||||
D1A2B3C62FFB84950033C403 /* SimulatorDebug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = DC152A95F1705B826BE8A56A /* Pods-suixinkanTests.simulatordebug.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 56GVN5RNVN;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.yuanzhixiang.suixinkanTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphonesimulator;
|
||||
SUPPORTED_PLATFORMS = iphonesimulator;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/suixinkan.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/suixinkan";
|
||||
};
|
||||
name = SimulatorDebug;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
@@ -728,6 +883,7 @@
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
93FF17DB2FFB84950033C304 /* Debug */,
|
||||
D1A2B3C52FFB84950033C402 /* SimulatorDebug */,
|
||||
93FF17DC2FFB84950033C304 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
@@ -737,6 +893,7 @@
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
93FF17D92FFB84950033C304 /* Debug */,
|
||||
D1A2B3C42FFB84950033C401 /* SimulatorDebug */,
|
||||
93FF17DA2FFB84950033C304 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
@@ -746,6 +903,7 @@
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
A1B2C3DC2FFB84950033C305 /* Debug */,
|
||||
D1A2B3C62FFB84950033C403 /* SimulatorDebug */,
|
||||
A1B2C3DA2FFB84950033C305 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "2660"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "93FF17C42FFB84950033C304"
|
||||
BuildableName = "suixinkan.app"
|
||||
BlueprintName = "suixinkan"
|
||||
ReferencedContainer = "container:suixinkan.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "SimulatorDebug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "93FF17C42FFB84950033C304"
|
||||
BuildableName = "suixinkan.app"
|
||||
BlueprintName = "suixinkan"
|
||||
ReferencedContainer = "container:suixinkan.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "A1B2C3D32FFB84950033C305"
|
||||
BuildableName = "suixinkanTests.xctest"
|
||||
BlueprintName = "suixinkanTests"
|
||||
ReferencedContainer = "container:suixinkan.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "SimulatorDebug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "93FF17C42FFB84950033C304"
|
||||
BuildableName = "suixinkan.app"
|
||||
BlueprintName = "suixinkan"
|
||||
ReferencedContainer = "container:suixinkan.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "SimulatorDebug"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "93FF17C42FFB84950033C304"
|
||||
BuildableName = "suixinkan.app"
|
||||
BlueprintName = "suixinkan"
|
||||
ReferencedContainer = "container:suixinkan.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "SimulatorDebug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -29,6 +29,7 @@ final class NetworkServices {
|
||||
let liveAPI: LiveAPI
|
||||
let cloudDriveAPI: CloudDriveAPI
|
||||
let messageCenterAPI: MessageCenterAPI
|
||||
let pushAPI: PushAPI
|
||||
let wildPhotographerReportAPI: WildPhotographerReportAPI
|
||||
let uploadAPI: UploadAPI
|
||||
let ossUploadService: OSSUploadService
|
||||
@@ -54,6 +55,7 @@ final class NetworkServices {
|
||||
liveAPI = LiveAPI(client: client)
|
||||
cloudDriveAPI = CloudDriveAPI(client: client)
|
||||
messageCenterAPI = MessageCenterAPI(client: client)
|
||||
pushAPI = PushAPI(client: client)
|
||||
wildPhotographerReportAPI = WildPhotographerReportAPI(client: client)
|
||||
uploadAPI = UploadAPI(client: client)
|
||||
ossUploadService = OSSUploadService(configService: uploadAPI)
|
||||
@@ -84,6 +86,7 @@ final class NetworkServices {
|
||||
liveAPI = LiveAPI(client: apiClient)
|
||||
cloudDriveAPI = CloudDriveAPI(client: apiClient)
|
||||
messageCenterAPI = MessageCenterAPI(client: apiClient)
|
||||
pushAPI = PushAPI(client: apiClient)
|
||||
wildPhotographerReportAPI = WildPhotographerReportAPI(client: apiClient)
|
||||
uploadAPI = UploadAPI(client: apiClient)
|
||||
ossUploadService = OSSUploadService(configService: uploadAPI)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import IQKeyboardManagerSwift
|
||||
import IQKeyboardToolbarManager
|
||||
import UIKit
|
||||
import UserNotifications
|
||||
|
||||
@main
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
@@ -18,9 +19,36 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
if AppStore.shared.session.privacyAgreementAccepted, !AppStore.shared.session.token.isEmpty {
|
||||
AMapBootstrap.configureIfNeeded()
|
||||
}
|
||||
PushNotificationManager.shared.initializeIfPrivacyAccepted(launchOptions: launchOptions)
|
||||
return true
|
||||
}
|
||||
|
||||
/// APNs 注册成功后将 Device Token 交给极光。
|
||||
func application(
|
||||
_ application: UIApplication,
|
||||
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
|
||||
) {
|
||||
PushNotificationManager.shared.handleDeviceToken(deviceToken)
|
||||
}
|
||||
|
||||
/// APNs 注册失败不阻断应用启动,仅交由推送管理器记录。
|
||||
func application(
|
||||
_ application: UIApplication,
|
||||
didFailToRegisterForRemoteNotificationsWithError error: Error
|
||||
) {
|
||||
PushNotificationManager.shared.handleRegistrationError(error)
|
||||
}
|
||||
|
||||
/// 处理后台静默通知并完成极光到达统计。
|
||||
func application(
|
||||
_ application: UIApplication,
|
||||
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
|
||||
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
|
||||
) {
|
||||
PushNotificationManager.shared.handleRemoteNotification(userInfo)
|
||||
completionHandler(.newData)
|
||||
}
|
||||
|
||||
// MARK: UISceneSession Lifecycle
|
||||
|
||||
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "payment_nalati_background.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.6 MiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "payment_nalati_logo.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 89 KiB |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"images" : [
|
||||
{ "filename" : "travel_album_cover_photo_icon.png", "idiom" : "universal", "scale" : "1x" },
|
||||
{ "filename" : "travel_album_cover_photo_icon@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||
{ "filename" : "travel_album_cover_photo_icon@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
|
After Width: | Height: | Size: 642 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"images" : [
|
||||
{ "filename" : "travel_album_header_background@1x.png", "idiom" : "universal", "scale" : "1x" },
|
||||
{ "filename" : "travel_album_header_background@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||
{ "filename" : "travel_album_header_background@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 225 KiB |
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"images" : [
|
||||
{ "filename" : "travel_album_sort_icon.png", "idiom" : "universal", "scale" : "1x" },
|
||||
{ "filename" : "travel_album_sort_icon@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||
{ "filename" : "travel_album_sort_icon@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
|
After Width: | Height: | Size: 258 B |
|
After Width: | Height: | Size: 588 B |
|
After Width: | Height: | Size: 970 B |
@@ -0,0 +1,30 @@
|
||||
import Foundation
|
||||
|
||||
/// 极光 Registration ID 上报能力,允许推送管理器注入测试替身。
|
||||
@MainActor
|
||||
protocol PushRegistrationServing: AnyObject {
|
||||
/// 将当前设备的极光 Registration ID 绑定到已登录业务账号。
|
||||
func registerJPushID(_ registrationID: String) async throws
|
||||
}
|
||||
|
||||
/// 推送 API,复用 Android 当前使用的 Registration ID 上报接口。
|
||||
@MainActor
|
||||
final class PushAPI: PushRegistrationServing {
|
||||
private let client: APIClient
|
||||
|
||||
/// 使用共享网络客户端创建推送 API。
|
||||
init(client: APIClient) {
|
||||
self.client = client
|
||||
}
|
||||
|
||||
/// 上报极光 Registration ID;后端字段名沿用 `jpush_reg_id`。
|
||||
func registerJPushID(_ registrationID: String) async throws {
|
||||
let _: EmptyPayload = try await client.send(
|
||||
APIRequest(
|
||||
method: .post,
|
||||
path: "/api/app/user/register-jpush-id",
|
||||
queryItems: [URLQueryItem(name: "jpush_reg_id", value: registrationID)]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import Foundation
|
||||
|
||||
/// 推送点击后可到达的业务页面。
|
||||
enum PushDestination: Sendable, Equatable {
|
||||
case paymentRecord
|
||||
case paymentDetails
|
||||
case messageCenter
|
||||
}
|
||||
|
||||
/// 极光/APNs payload 解析结果,仅提取业务消息类型用于点击路由。
|
||||
struct PushPayload: Sendable, Equatable {
|
||||
private let type: String
|
||||
|
||||
/// 从系统通知 userInfo 提取顶层或推送包装层中的业务消息类型。
|
||||
nonisolated init(userInfo: [AnyHashable: Any]) {
|
||||
type = Self.extractType(from: userInfo)
|
||||
}
|
||||
|
||||
/// 从可发送的字符串字典创建 payload,仅保留业务消息类型。
|
||||
nonisolated init(values: [String: String]) {
|
||||
type = Self.normalizedType(values["type"])
|
||||
}
|
||||
|
||||
/// 可发送的规范化字段快照。
|
||||
nonisolated var normalizedValues: [String: String] {
|
||||
type.isEmpty ? [:] : ["type": type]
|
||||
}
|
||||
|
||||
/// 仅根据后端业务消息类型解析目标页面。
|
||||
nonisolated var destination: PushDestination {
|
||||
switch type {
|
||||
case "1":
|
||||
return .paymentRecord
|
||||
case "6":
|
||||
return .paymentDetails
|
||||
default:
|
||||
return .messageCenter
|
||||
}
|
||||
}
|
||||
|
||||
private nonisolated static func extractType(
|
||||
from dictionary: [AnyHashable: Any],
|
||||
depth: Int = 0
|
||||
) -> String {
|
||||
guard depth <= 4 else { return "" }
|
||||
let directType = normalizedType(dictionary["type"])
|
||||
guard directType.isEmpty else { return directType }
|
||||
|
||||
for key in nestedTypeContainerKeys {
|
||||
guard let value = dictionary[key] else { continue }
|
||||
if let nested = value as? [AnyHashable: Any] {
|
||||
let nestedType = extractType(from: nested, depth: depth + 1)
|
||||
if !nestedType.isEmpty { return nestedType }
|
||||
continue
|
||||
}
|
||||
|
||||
guard let text = value as? String,
|
||||
let data = text.data(using: .utf8),
|
||||
let object = try? JSONSerialization.jsonObject(with: data),
|
||||
let nested = object as? [String: Any]
|
||||
else { continue }
|
||||
|
||||
let bridged = Dictionary(uniqueKeysWithValues: nested.map {
|
||||
(AnyHashable($0.key), $0.value)
|
||||
})
|
||||
let nestedType = extractType(from: bridged, depth: depth + 1)
|
||||
if !nestedType.isEmpty { return nestedType }
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
private nonisolated static func normalizedType(_ value: Any?) -> String {
|
||||
switch value {
|
||||
case let string as String:
|
||||
return string.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
case let number as NSNumber:
|
||||
return number.stringValue
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
private nonisolated static let nestedTypeContainerKeys = [
|
||||
"extras", "extra", "JMessageExtra", "n_extras",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
import UIKit
|
||||
import UserNotifications
|
||||
|
||||
/// App 桌面图标角标设置能力,便于业务同步未读消息数量并进行测试替换。
|
||||
@MainActor
|
||||
protocol ApplicationIconBadgeSetting: AnyObject {
|
||||
/// 将桌面图标角标更新为指定非负数量。
|
||||
func setBadgeCount(_ count: Int) async
|
||||
}
|
||||
|
||||
/// 使用 UserNotifications 更新 App 桌面图标角标的系统实现。
|
||||
@MainActor
|
||||
final class SystemApplicationIconBadgeSetter: ApplicationIconBadgeSetting {
|
||||
/// 全局角标设置器。
|
||||
static let shared = SystemApplicationIconBadgeSetter()
|
||||
|
||||
private init() {}
|
||||
|
||||
func setBadgeCount(_ count: Int) async {
|
||||
do {
|
||||
try await UNUserNotificationCenter.current().setBadgeCount(max(count, 0))
|
||||
} catch {
|
||||
#if DEBUG
|
||||
print("Update application icon badge failed: \(error.localizedDescription)")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 极光 SDK 的最小能力边界,便于推送生命周期逻辑使用测试替身。
|
||||
@MainActor
|
||||
protocol PushSDKProviding: AnyObject {
|
||||
/// 初始化极光长连接与 APNs 代理服务。
|
||||
func initialize(launchOptions: [UIApplication.LaunchOptionsKey: Any]?, isProduction: Bool)
|
||||
|
||||
/// 请求系统通知权限并注册极光通知代理。
|
||||
func requestAuthorization(delegate: JPUSHRegisterDelegate)
|
||||
|
||||
/// 将 APNs Device Token 注册给极光。
|
||||
func registerDeviceToken(_ deviceToken: Data)
|
||||
|
||||
/// 获取极光 Registration ID。
|
||||
func fetchRegistrationID(completion: @escaping (Int32, String?) -> Void)
|
||||
}
|
||||
|
||||
/// 跨越极光 `@Sendable` 回调边界的只读闭包容器。
|
||||
private final class RegistrationIDCompletionBox: @unchecked Sendable {
|
||||
let completion: (Int32, String?) -> Void
|
||||
|
||||
init(completion: @escaping (Int32, String?) -> Void) {
|
||||
self.completion = completion
|
||||
}
|
||||
}
|
||||
|
||||
/// 生产环境极光 SDK 适配器,集中保存 AppKey 和渠道配置。
|
||||
@MainActor
|
||||
final class JPushSDKAdapter: PushSDKProviding {
|
||||
private static let appKey = "194a633c2a6b79c2f1033c40"
|
||||
private static let channel = "App Store"
|
||||
|
||||
/// 初始化极光 SDK;Debug 输出详细日志,Release 只保留告警和错误。
|
||||
func initialize(launchOptions: [UIApplication.LaunchOptionsKey: Any]?, isProduction: Bool) {
|
||||
JPUSHService.setup(
|
||||
withOption: launchOptions,
|
||||
appKey: Self.appKey,
|
||||
channel: Self.channel,
|
||||
apsForProduction: isProduction
|
||||
)
|
||||
#if DEBUG
|
||||
JPUSHService.setDebugMode()
|
||||
#else
|
||||
JPUSHService.setLogOFF()
|
||||
#endif
|
||||
}
|
||||
|
||||
/// 请求 alert、badge、sound 三类通知权限。
|
||||
func requestAuthorization(delegate: JPUSHRegisterDelegate) {
|
||||
let entity = JPUSHRegisterEntity()
|
||||
entity.types = 1 | 2 | 4
|
||||
JPUSHService.register(forRemoteNotificationConfig: entity, delegate: delegate)
|
||||
}
|
||||
|
||||
/// 将 APNs Device Token 交给极光。
|
||||
func registerDeviceToken(_ deviceToken: Data) {
|
||||
JPUSHService.registerDeviceToken(deviceToken)
|
||||
}
|
||||
|
||||
/// 异步获取当前设备 Registration ID。
|
||||
func fetchRegistrationID(completion: @escaping (Int32, String?) -> Void) {
|
||||
let box = RegistrationIDCompletionBox(completion: completion)
|
||||
JPUSHService.registrationIDCompletionHandler { resultCode, registrationID in
|
||||
box.completion(resultCode, registrationID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 推送生命周期管理器,负责隐私门禁、权限、Registration ID 绑定及通知点击分发。
|
||||
@MainActor
|
||||
final class PushNotificationManager: NSObject {
|
||||
/// 全局推送管理器。
|
||||
static let shared = PushNotificationManager()
|
||||
|
||||
/// 推送相关的本地持久化键。
|
||||
private enum Key {
|
||||
static let registrationID = "key_jpush_registration_id"
|
||||
static let uploadedRegistrationIDSuffix = "jpush_uploaded_registration_id"
|
||||
}
|
||||
|
||||
private let sdk: any PushSDKProviding
|
||||
private let api: any PushRegistrationServing
|
||||
private let appStore: AppStore
|
||||
private let defaults: UserDefaults
|
||||
private let router: any PushRouting
|
||||
private let applicationIconBadgeSetter: any ApplicationIconBadgeSetting
|
||||
|
||||
private var isInitialized = false
|
||||
private var didRequestAuthorization = false
|
||||
private var uploadTask: Task<Void, Never>?
|
||||
private var queuedForcedUpload = false
|
||||
private var isFetchingRegistrationID = false
|
||||
private var queuedForcedFetch = false
|
||||
|
||||
/// 创建推送管理器;生产环境使用共享依赖,测试可注入替身。
|
||||
init(
|
||||
sdk: (any PushSDKProviding)? = nil,
|
||||
api: (any PushRegistrationServing)? = nil,
|
||||
appStore: AppStore = .shared,
|
||||
defaults: UserDefaults = .standard,
|
||||
router: (any PushRouting)? = nil,
|
||||
applicationIconBadgeSetter: (any ApplicationIconBadgeSetting)? = nil
|
||||
) {
|
||||
self.sdk = sdk ?? JPushSDKAdapter()
|
||||
self.api = api ?? NetworkServices.shared.pushAPI
|
||||
self.appStore = appStore
|
||||
self.defaults = defaults
|
||||
self.router = router ?? PushRouteCoordinator.shared
|
||||
self.applicationIconBadgeSetter = applicationIconBadgeSetter ?? SystemApplicationIconBadgeSetter.shared
|
||||
super.init()
|
||||
}
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
/// 绑定当前 Scene 的主窗口。
|
||||
func attach(window: UIWindow) {
|
||||
router.attach(window: window)
|
||||
}
|
||||
|
||||
/// 仅在用户已同意隐私协议后初始化极光 SDK。
|
||||
func initializeIfPrivacyAccepted(
|
||||
launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
||||
) {
|
||||
guard appStore.session.privacyAgreementAccepted, !isInitialized else { return }
|
||||
isInitialized = true
|
||||
sdk.initialize(launchOptions: launchOptions, isProduction: Self.isProductionBuild)
|
||||
observeJPushNetworkLogin()
|
||||
refreshRegistrationID(forceUpload: false)
|
||||
}
|
||||
|
||||
/// 登录成功后请求通知权限,并强制绑定当前账号。
|
||||
func handleLoginCompleted() {
|
||||
initializeIfPrivacyAccepted()
|
||||
guard isInitialized, appStore.session.isLoggedIn else { return }
|
||||
if !didRequestAuthorization {
|
||||
didRequestAuthorization = true
|
||||
sdk.requestAuthorization(delegate: self)
|
||||
}
|
||||
bindCurrentAccount()
|
||||
}
|
||||
|
||||
/// 账号切换后把同一设备重新绑定到新的业务账号。
|
||||
func handleAccountSwitched() {
|
||||
guard appStore.session.isLoggedIn else { return }
|
||||
bindCurrentAccount()
|
||||
}
|
||||
|
||||
/// 退出登录时停止账号上报并清除未完成的业务路由。
|
||||
func handleLogout() {
|
||||
uploadTask?.cancel()
|
||||
uploadTask = nil
|
||||
queuedForcedUpload = false
|
||||
router.resetPendingRoute()
|
||||
Task { await updateApplicationIconBadgeCount(0) }
|
||||
}
|
||||
|
||||
/// 将桌面 App Icon 角标更新为最新未读消息数量。
|
||||
func updateApplicationIconBadgeCount(_ count: Int) async {
|
||||
await applicationIconBadgeSetter.setBadgeCount(max(count, 0))
|
||||
}
|
||||
|
||||
/// App 回到前台时补偿失败或尚未完成的 Registration ID 上报。
|
||||
func retryPendingRegistrationUpload() {
|
||||
guard appStore.session.isLoggedIn else { return }
|
||||
uploadCachedRegistrationID(force: false)
|
||||
refreshRegistrationID(forceUpload: false)
|
||||
}
|
||||
|
||||
/// 登录根页面建立后继续执行通知点击暂存的路由。
|
||||
func routePendingNotificationIfPossible() {
|
||||
router.routePendingIfPossible()
|
||||
}
|
||||
|
||||
/// APNs 注册成功后把 Device Token 注册给极光。
|
||||
func handleDeviceToken(_ deviceToken: Data) {
|
||||
guard isInitialized else { return }
|
||||
sdk.registerDeviceToken(deviceToken)
|
||||
refreshRegistrationID(forceUpload: false)
|
||||
}
|
||||
|
||||
/// APNs 注册失败只记录调试信息,不阻断登录和主业务。
|
||||
func handleRegistrationError(_ error: Error) {
|
||||
#if DEBUG
|
||||
print("JPush APNs registration failed: \(error.localizedDescription)")
|
||||
#endif
|
||||
}
|
||||
|
||||
/// 处理冷启动或运行中通知点击。
|
||||
func handleNotificationTap(
|
||||
payload: PushPayload,
|
||||
requestIdentifier: String
|
||||
) {
|
||||
router.handle(
|
||||
destination: payload.destination,
|
||||
requestIdentifier: requestIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
/// 处理 Scene 冷启动携带的系统通知响应,并向极光上报点击。
|
||||
func handleNotificationResponse(_ response: UNNotificationResponse) {
|
||||
let userInfo = response.notification.request.content.userInfo
|
||||
Self.logReceivedPush(userInfo, source: "tap(cold-start)")
|
||||
JPUSHService.handleRemoteNotification(userInfo)
|
||||
handleNotificationTap(
|
||||
payload: PushPayload(userInfo: userInfo),
|
||||
requestIdentifier: response.notification.request.identifier
|
||||
)
|
||||
}
|
||||
|
||||
/// 处理后台静默通知并向极光上报到达,不触发页面跳转。
|
||||
func handleRemoteNotification(_ userInfo: [AnyHashable: Any]) {
|
||||
Self.logReceivedPush(userInfo, source: "background")
|
||||
JPUSHService.handleRemoteNotification(userInfo)
|
||||
NotificationCenter.default.post(name: NotificationName.unreadMessageCountDidChange, object: nil)
|
||||
}
|
||||
|
||||
/// 将收到的推送 payload 打印到控制台,便于联调排查。
|
||||
private nonisolated static func logReceivedPush(
|
||||
_ userInfo: [AnyHashable: Any],
|
||||
source: String
|
||||
) {
|
||||
#if DEBUG
|
||||
let payloadText: String
|
||||
let normalized = Dictionary(uniqueKeysWithValues: userInfo.compactMap { key, value in
|
||||
(key as? String).map { ($0, value) }
|
||||
})
|
||||
if JSONSerialization.isValidJSONObject(normalized),
|
||||
let data = try? JSONSerialization.data(
|
||||
withJSONObject: normalized,
|
||||
options: [.prettyPrinted, .sortedKeys]
|
||||
),
|
||||
let text = String(data: data, encoding: .utf8) {
|
||||
payloadText = text
|
||||
} else {
|
||||
payloadText = String(describing: userInfo)
|
||||
}
|
||||
print("[Push] received (\(source)):\n\(payloadText)")
|
||||
#endif
|
||||
}
|
||||
|
||||
private func observeJPushNetworkLogin() {
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(handleJPushNetworkLogin),
|
||||
name: .jpfNetworkDidLogin,
|
||||
object: nil,
|
||||
)
|
||||
}
|
||||
|
||||
@objc private func handleJPushNetworkLogin() {
|
||||
refreshRegistrationID(forceUpload: false)
|
||||
}
|
||||
|
||||
private func refreshRegistrationID(forceUpload: Bool) {
|
||||
guard isInitialized else { return }
|
||||
queuedForcedFetch = queuedForcedFetch || forceUpload
|
||||
guard !isFetchingRegistrationID else { return }
|
||||
isFetchingRegistrationID = true
|
||||
sdk.fetchRegistrationID { [weak self] resultCode, registrationID in
|
||||
Task { @MainActor in
|
||||
guard let self else { return }
|
||||
let shouldForceUpload = self.queuedForcedFetch
|
||||
self.queuedForcedFetch = false
|
||||
self.isFetchingRegistrationID = false
|
||||
guard resultCode == 0 else { return }
|
||||
let normalized = registrationID?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
guard !normalized.isEmpty else { return }
|
||||
self.defaults.set(normalized, forKey: Key.registrationID)
|
||||
self.upload(registrationID: normalized, force: shouldForceUpload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func uploadCachedRegistrationID(force: Bool) {
|
||||
let registrationID = cachedRegistrationID
|
||||
guard !registrationID.isEmpty else { return }
|
||||
upload(registrationID: registrationID, force: force)
|
||||
}
|
||||
|
||||
private func bindCurrentAccount() {
|
||||
if cachedRegistrationID.isEmpty {
|
||||
refreshRegistrationID(forceUpload: true)
|
||||
} else {
|
||||
uploadCachedRegistrationID(force: true)
|
||||
refreshRegistrationID(forceUpload: false)
|
||||
}
|
||||
}
|
||||
|
||||
private var cachedRegistrationID: String {
|
||||
defaults.string(forKey: Key.registrationID)?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
}
|
||||
|
||||
private func upload(registrationID: String, force: Bool) {
|
||||
guard appStore.session.isLoggedIn,
|
||||
let uploadedKey = appStore.session.accountScopedKey(Key.uploadedRegistrationIDSuffix)
|
||||
else { return }
|
||||
|
||||
if !force, defaults.string(forKey: uploadedKey) == registrationID {
|
||||
return
|
||||
}
|
||||
if uploadTask != nil {
|
||||
queuedForcedUpload = queuedForcedUpload || force
|
||||
return
|
||||
}
|
||||
|
||||
let accountScope = appStore.session.accountCachePrefix
|
||||
uploadTask = Task { [weak self] in
|
||||
guard let self else { return }
|
||||
var succeeded = false
|
||||
do {
|
||||
try await self.api.registerJPushID(registrationID)
|
||||
if self.appStore.session.accountCachePrefix == accountScope {
|
||||
self.defaults.set(registrationID, forKey: uploadedKey)
|
||||
}
|
||||
succeeded = true
|
||||
} catch is CancellationError {
|
||||
// 退出登录时取消,不视为失败重试。
|
||||
} catch {
|
||||
#if DEBUG
|
||||
print("JPush Registration ID upload failed: \(error.localizedDescription)")
|
||||
#endif
|
||||
}
|
||||
|
||||
self.uploadTask = nil
|
||||
let shouldForceAgain = self.queuedForcedUpload
|
||||
self.queuedForcedUpload = false
|
||||
if succeeded, shouldForceAgain {
|
||||
self.uploadCachedRegistrationID(force: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private nonisolated static var isProductionBuild: Bool {
|
||||
#if DEBUG
|
||||
false
|
||||
#else
|
||||
true
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
extension PushNotificationManager: JPUSHRegisterDelegate {
|
||||
/// 前台通知只展示系统 UI,不自动执行业务路由。
|
||||
nonisolated func jpushNotificationCenter(
|
||||
_ center: UNUserNotificationCenter,
|
||||
willPresent notification: UNNotification,
|
||||
withCompletionHandler completionHandler: @escaping (Int) -> Void
|
||||
) {
|
||||
let userInfo = notification.request.content.userInfo
|
||||
Self.logReceivedPush(userInfo, source: "foreground")
|
||||
JPUSHService.handleRemoteNotification(userInfo)
|
||||
Task { @MainActor in
|
||||
NotificationCenter.default.post(name: NotificationName.unreadMessageCountDidChange, object: nil)
|
||||
}
|
||||
let options: UNNotificationPresentationOptions = [.banner, .list, .sound, .badge]
|
||||
completionHandler(Int(options.rawValue))
|
||||
}
|
||||
|
||||
/// 用户点击通知后上报极光统计,并在主 Actor 执行业务路由。
|
||||
nonisolated func jpushNotificationCenter(
|
||||
_ center: UNUserNotificationCenter,
|
||||
didReceive response: UNNotificationResponse,
|
||||
withCompletionHandler completionHandler: @escaping () -> Void
|
||||
) {
|
||||
let userInfo = response.notification.request.content.userInfo
|
||||
Self.logReceivedPush(userInfo, source: "tap")
|
||||
JPUSHService.handleRemoteNotification(userInfo)
|
||||
let payload = PushPayload(userInfo: userInfo)
|
||||
let requestIdentifier = response.notification.request.identifier
|
||||
Task { @MainActor [weak self] in
|
||||
self?.handleNotificationTap(
|
||||
payload: payload,
|
||||
requestIdentifier: requestIdentifier
|
||||
)
|
||||
}
|
||||
completionHandler()
|
||||
}
|
||||
|
||||
/// 系统通知设置入口回调,首期无需额外应用内页面。
|
||||
nonisolated func jpushNotificationCenter(
|
||||
_ center: UNUserNotificationCenter,
|
||||
openSettingsFor notification: UNNotification
|
||||
) {}
|
||||
|
||||
/// 极光通知授权状态回调,权限变化由系统设置管理。
|
||||
nonisolated func jpushNotificationAuthorization(
|
||||
_ status: JPAuthorizationStatus,
|
||||
withInfo info: [AnyHashable: Any]?
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import UIKit
|
||||
|
||||
/// 推送点击路由能力,允许生命周期管理器与 UIKit 导航解耦并支持测试替身。
|
||||
@MainActor
|
||||
protocol PushRouting: AnyObject {
|
||||
/// 绑定当前 Scene 的主窗口。
|
||||
func attach(window: UIWindow)
|
||||
|
||||
/// 处理一次通知点击;未登录时暂存到登录成功后继续。
|
||||
func handle(destination: PushDestination, requestIdentifier: String)
|
||||
|
||||
/// 主界面建立后执行尚未完成的通知路由。
|
||||
func routePendingIfPossible()
|
||||
|
||||
/// 清除退出登录前遗留的待处理路由。
|
||||
func resetPendingRoute()
|
||||
}
|
||||
|
||||
/// 集中式推送路由器,负责冷启动去重、登录等待和跨 Tab 导航。
|
||||
@MainActor
|
||||
final class PushRouteCoordinator: PushRouting {
|
||||
/// 全局推送路由器。
|
||||
static let shared = PushRouteCoordinator()
|
||||
|
||||
/// 等待登录或主界面建立后执行的一次性推送路由。
|
||||
private struct PendingRoute {
|
||||
let destination: PushDestination
|
||||
let requestIdentifier: String
|
||||
}
|
||||
|
||||
private weak var window: UIWindow?
|
||||
private var pendingRoute: PendingRoute?
|
||||
private var handledRequestIdentifiers: [String] = []
|
||||
private let appStore: AppStore
|
||||
|
||||
/// 创建路由器并注入应用状态。
|
||||
init(appStore: AppStore = .shared) {
|
||||
self.appStore = appStore
|
||||
}
|
||||
|
||||
/// 绑定当前 Scene 的主窗口。
|
||||
func attach(window: UIWindow) {
|
||||
self.window = window
|
||||
}
|
||||
|
||||
/// 处理通知点击;相同 request identifier 只执行一次。
|
||||
func handle(destination: PushDestination, requestIdentifier: String) {
|
||||
let identifier = requestIdentifier.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if !identifier.isEmpty, handledRequestIdentifiers.contains(identifier) {
|
||||
return
|
||||
}
|
||||
markHandled(identifier)
|
||||
|
||||
let route = PendingRoute(destination: destination, requestIdentifier: identifier)
|
||||
guard appStore.session.isLoggedIn, mainTabController != nil else {
|
||||
pendingRoute = route
|
||||
return
|
||||
}
|
||||
perform(route)
|
||||
}
|
||||
|
||||
/// 登录完成、根页面切换后继续执行最后一次待处理路由。
|
||||
func routePendingIfPossible() {
|
||||
guard appStore.session.isLoggedIn,
|
||||
mainTabController != nil,
|
||||
let route = pendingRoute
|
||||
else { return }
|
||||
pendingRoute = nil
|
||||
perform(route)
|
||||
}
|
||||
|
||||
/// 用户退出登录时丢弃之前账号尚未执行的跳转。
|
||||
func resetPendingRoute() {
|
||||
pendingRoute = nil
|
||||
}
|
||||
|
||||
private var mainTabController: MainTabBarController? {
|
||||
window?.rootViewController as? MainTabBarController
|
||||
}
|
||||
|
||||
private func perform(_ route: PendingRoute) {
|
||||
guard let mainTabController else {
|
||||
pendingRoute = route
|
||||
return
|
||||
}
|
||||
|
||||
mainTabController.dismiss(animated: false)
|
||||
mainTabController.openPushDestination(route.destination)
|
||||
}
|
||||
|
||||
private func markHandled(_ identifier: String) {
|
||||
guard !identifier.isEmpty else { return }
|
||||
handledRequestIdentifiers.append(identifier)
|
||||
if handledRequestIdentifiers.count > 50 {
|
||||
handledRequestIdentifiers.removeFirst(handledRequestIdentifiers.count - 50)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,10 +70,10 @@ enum AuthSessionHelper {
|
||||
AppStore.shared.session.userId = String(user.businessUserId)
|
||||
AppStore.shared.session.accountType = .scenicUser
|
||||
AppStore.shared.session.accountDisplayName = firstNonEmpty(
|
||||
user.scenicName, user.nickname, user.realName, user.username
|
||||
user.scenicName, user.nickname, user.realName
|
||||
)
|
||||
AppStore.shared.session.userName = firstNonEmpty(user.nickname, user.username, user.realName)
|
||||
AppStore.shared.session.realName = firstNonEmpty(user.realName, user.nickname, user.username)
|
||||
AppStore.shared.session.userName = firstNonEmpty(user.nickname, user.realName)
|
||||
AppStore.shared.session.realName = user.realName
|
||||
AppStore.shared.session.phone = user.phone
|
||||
AppStore.shared.session.currentScenicId = user.scenicId
|
||||
AppStore.shared.session.currentScenicName = user.scenicName
|
||||
@@ -90,10 +90,10 @@ enum AuthSessionHelper {
|
||||
AppStore.shared.session.userId = String(user.businessUserId)
|
||||
AppStore.shared.session.accountType = .storeUser
|
||||
AppStore.shared.session.accountDisplayName = firstNonEmpty(
|
||||
user.storeName, user.scenicName, user.realName, user.userName, user.username
|
||||
user.storeName, user.scenicName, user.realName
|
||||
)
|
||||
AppStore.shared.session.userName = firstNonEmpty(user.userName, user.username, user.realName)
|
||||
AppStore.shared.session.realName = firstNonEmpty(user.realName, user.userName, user.username)
|
||||
AppStore.shared.session.userName = user.realName
|
||||
AppStore.shared.session.realName = user.realName
|
||||
AppStore.shared.session.phone = user.phone
|
||||
AppStore.shared.session.avatar = user.avatar
|
||||
AppStore.shared.session.currentScenicId = user.scenicId
|
||||
|
||||
@@ -196,7 +196,6 @@ struct V9ScenicUser: Decodable, Equatable {
|
||||
let userId: Int
|
||||
let scenicUserId: Int
|
||||
let ssUserId: Int
|
||||
let username: String
|
||||
let realName: String
|
||||
let nickname: String
|
||||
let phone: String
|
||||
@@ -212,7 +211,7 @@ struct V9ScenicUser: Decodable, Equatable {
|
||||
}
|
||||
|
||||
var displayName: String {
|
||||
scenicName.nonEmpty ?? nickname.nonEmpty ?? realName.nonEmpty ?? username
|
||||
scenicName.nonEmpty ?? nickname.nonEmpty ?? realName
|
||||
}
|
||||
|
||||
func toAccountSwitchAccount() -> AccountSwitchAccount {
|
||||
@@ -238,8 +237,6 @@ struct V9ScenicUser: Decodable, Equatable {
|
||||
case userId = "user_id"
|
||||
case scenicUserId = "scenic_user_id"
|
||||
case ssUserId = "ss_user_id"
|
||||
case username = "user_name"
|
||||
case legacyUsername = "username"
|
||||
case realName = "real_name"
|
||||
case nickname
|
||||
case phone
|
||||
@@ -258,9 +255,6 @@ struct V9ScenicUser: Decodable, Equatable {
|
||||
userId = try container.decodeLossyInt(forKey: .userId) ?? 0
|
||||
scenicUserId = try container.decodeLossyInt(forKey: .scenicUserId) ?? 0
|
||||
ssUserId = try container.decodeLossyInt(forKey: .ssUserId) ?? 0
|
||||
let preferredUsername = try container.decodeLossyString(forKey: .username)
|
||||
let legacyUsername = try container.decodeLossyString(forKey: .legacyUsername)
|
||||
username = preferredUsername.nonEmpty ?? legacyUsername
|
||||
realName = try container.decodeLossyString(forKey: .realName)
|
||||
nickname = try container.decodeLossyString(forKey: .nickname)
|
||||
phone = try container.decodeLossyString(forKey: .phone)
|
||||
@@ -281,8 +275,6 @@ struct V9StoreUser: Decodable, Equatable {
|
||||
let id: Int
|
||||
let userId: Int
|
||||
let storeUserId: Int
|
||||
let username: String
|
||||
let userName: String
|
||||
let realName: String
|
||||
let phone: String
|
||||
let avatar: String
|
||||
@@ -300,14 +292,14 @@ struct V9StoreUser: Decodable, Equatable {
|
||||
}
|
||||
|
||||
var displayName: String {
|
||||
storeName.nonEmpty ?? scenicName.nonEmpty ?? realName.nonEmpty ?? userName.nonEmpty ?? username
|
||||
storeName.nonEmpty ?? scenicName.nonEmpty ?? realName
|
||||
}
|
||||
|
||||
func toAccountSwitchAccount() -> AccountSwitchAccount {
|
||||
AccountSwitchAccount(
|
||||
accountType: accountType.nonEmpty ?? Self.accountTypeValue,
|
||||
businessUserId: businessUserId,
|
||||
title: storeName.nonEmpty ?? scenicName.nonEmpty ?? realName.nonEmpty ?? userName.nonEmpty ?? "门店账号",
|
||||
title: storeName.nonEmpty ?? scenicName.nonEmpty ?? realName.nonEmpty ?? "门店账号",
|
||||
subtitle: joinAccountSubtitle(scenicName, roleName),
|
||||
phone: phone,
|
||||
realName: realName,
|
||||
@@ -325,8 +317,6 @@ struct V9StoreUser: Decodable, Equatable {
|
||||
case id
|
||||
case userId = "user_id"
|
||||
case storeUserId = "store_user_id"
|
||||
case username
|
||||
case userName = "user_name"
|
||||
case realName = "real_name"
|
||||
case phone
|
||||
case avatar
|
||||
@@ -346,8 +336,6 @@ struct V9StoreUser: Decodable, Equatable {
|
||||
id = try container.decodeLossyInt(forKey: .id) ?? 0
|
||||
userId = try container.decodeLossyInt(forKey: .userId) ?? 0
|
||||
storeUserId = try container.decodeLossyInt(forKey: .storeUserId) ?? 0
|
||||
username = try container.decodeLossyString(forKey: .username)
|
||||
userName = try container.decodeLossyString(forKey: .userName)
|
||||
realName = try container.decodeLossyString(forKey: .realName)
|
||||
phone = try container.decodeLossyString(forKey: .phone)
|
||||
avatar = try container.decodeLossyString(forKey: .avatar)
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// CommissionRateLogViewModel.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 获客员分成比例修改记录 ViewModel,负责刷新、分页与重复项合并。
|
||||
final class CommissionRateLogViewModel {
|
||||
private enum Constants {
|
||||
static let pageSize = 10
|
||||
}
|
||||
|
||||
private(set) var items: [CommissionRateLogEntity] = []
|
||||
private(set) var isRefreshing = false
|
||||
private(set) var isLoadingMore = false
|
||||
private(set) var initialLoading = true
|
||||
private(set) var canLoadMore = false
|
||||
|
||||
var onStateChange: (() -> Void)?
|
||||
var onShowMessage: ((String) -> Void)?
|
||||
|
||||
private let saleUserId: Int
|
||||
private var total = 0
|
||||
private var lastLoadedPage = 0
|
||||
private var isLoading = false
|
||||
|
||||
init(saleUserId: Int) {
|
||||
self.saleUserId = saleUserId
|
||||
}
|
||||
|
||||
/// 重新加载第一页修改记录。
|
||||
func refresh(api: OrderAPI) async {
|
||||
await load(api: api, page: 1, append: false)
|
||||
}
|
||||
|
||||
/// 在存在下一页时加载更多修改记录。
|
||||
func loadMore(api: OrderAPI) async {
|
||||
guard canLoadMore, !isRefreshing, !isLoadingMore else { return }
|
||||
await load(api: api, page: lastLoadedPage + 1, append: true)
|
||||
}
|
||||
|
||||
private func load(api: OrderAPI, page: Int, append: Bool) async {
|
||||
guard saleUserId > 0 else {
|
||||
initialLoading = false
|
||||
onShowMessage?("获客员信息无效")
|
||||
notifyStateChange()
|
||||
return
|
||||
}
|
||||
guard !isLoading else { return }
|
||||
isLoading = true
|
||||
if append {
|
||||
isLoadingMore = true
|
||||
} else {
|
||||
isRefreshing = true
|
||||
}
|
||||
notifyStateChange()
|
||||
|
||||
defer {
|
||||
isLoading = false
|
||||
initialLoading = false
|
||||
if append {
|
||||
isLoadingMore = false
|
||||
} else {
|
||||
isRefreshing = false
|
||||
}
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
do {
|
||||
let response = try await api.saleUserCommissionRateLogs(
|
||||
saleUserId: saleUserId,
|
||||
page: page,
|
||||
pageSize: Constants.pageSize
|
||||
)
|
||||
if append {
|
||||
let existingIDs = Set(items.map(\.id))
|
||||
items.append(contentsOf: response.list.filter { !existingIDs.contains($0.id) })
|
||||
} else {
|
||||
var seenIDs = Set<Int>()
|
||||
items = response.list.filter { seenIDs.insert($0.id).inserted }
|
||||
}
|
||||
total = max(0, response.total)
|
||||
lastLoadedPage = page
|
||||
canLoadMore = items.count < total && !response.list.isEmpty
|
||||
} catch {
|
||||
onShowMessage?(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private func notifyStateChange() {
|
||||
onStateChange?()
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,40 @@ import Foundation
|
||||
struct RolePermissionResponse: Codable, Equatable, Sendable {
|
||||
let role: RoleInfo
|
||||
let scenic: [ScenicInfo]
|
||||
let store: [RolePermissionStoreInfo]
|
||||
|
||||
init(role: RoleInfo, scenic: [ScenicInfo] = []) {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case role
|
||||
case scenic
|
||||
case store
|
||||
}
|
||||
|
||||
init(
|
||||
role: RoleInfo,
|
||||
scenic: [ScenicInfo] = [],
|
||||
store: [RolePermissionStoreInfo] = []
|
||||
) {
|
||||
self.role = role
|
||||
self.scenic = scenic
|
||||
self.store = store
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
role = try container.decode(RoleInfo.self, forKey: .role)
|
||||
scenic = try container.decodeIfPresent([ScenicInfo].self, forKey: .scenic) ?? []
|
||||
store = try container.decodeIfPresent([RolePermissionStoreInfo].self, forKey: .store) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
/// 当前角色账号可使用的店铺摘要,来源于 `role-permission` 的 `store` 字段。
|
||||
struct RolePermissionStoreInfo: Codable, Equatable, Sendable {
|
||||
let id: Int
|
||||
let name: String
|
||||
|
||||
init(id: Int = 0, name: String = "") {
|
||||
self.id = id
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,18 +17,23 @@ final class HomeCommonMenuStore {
|
||||
/// 读取已保存的常用 URI;无记录时返回空数组。
|
||||
func savedCommonURIs(accountScope: String, roleCode: String) -> [String] {
|
||||
guard let key = storageKey(accountScope: accountScope, roleCode: roleCode) else { return [] }
|
||||
return defaults.stringArray(forKey: key) ?? []
|
||||
let saved = defaults.stringArray(forKey: key) ?? []
|
||||
let sanitized = saved.filter(HomeMenuCatalog.isCustomizable(uri:))
|
||||
if sanitized != saved {
|
||||
defaults.set(sanitized, forKey: key)
|
||||
}
|
||||
return sanitized
|
||||
}
|
||||
|
||||
/// 保存常用 URI 列表。
|
||||
func saveCommonURIs(_ uris: [String], accountScope: String, roleCode: String) {
|
||||
guard let key = storageKey(accountScope: accountScope, roleCode: roleCode) else { return }
|
||||
defaults.set(uris, forKey: key)
|
||||
defaults.set(uris.filter(HomeMenuCatalog.isCustomizable(uri:)), forKey: key)
|
||||
}
|
||||
|
||||
/// 根据权限生成默认常用 URI(最多 4 个)。
|
||||
static func defaultCommonURIs(from permissions: [HomePermissionItem]) -> [String] {
|
||||
let uris = permissions.map(\.uri)
|
||||
let uris = permissions.map(\.uri).filter(HomeMenuCatalog.isCustomizable(uri:))
|
||||
return uris.count > 4 ? Array(uris.prefix(4)) : uris
|
||||
}
|
||||
|
||||
@@ -51,8 +56,9 @@ final class HomeCommonMenuStore {
|
||||
commonURIs: [String]
|
||||
) -> (common: [HomeMenuItem], more: [HomeMenuItem]) {
|
||||
let commonSet = Set(commonURIs)
|
||||
let common = allMenus.filter { commonSet.contains($0.uri) }
|
||||
let more = allMenus.filter { !commonSet.contains($0.uri) }
|
||||
let customizableMenus = allMenus.filter { HomeMenuCatalog.isCustomizable(uri: $0.uri) }
|
||||
let common = customizableMenus.filter { commonSet.contains($0.uri) }
|
||||
let more = customizableMenus.filter { !commonSet.contains($0.uri) }
|
||||
return (common, more)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import Foundation
|
||||
/// 首页菜单静态 catalog,与服务端 permission URI 取交集后展示。
|
||||
enum HomeMenuCatalog {
|
||||
|
||||
private static let fixedHomeEntryURIs: Set<String> = ["message_center"]
|
||||
|
||||
/// 全部已知菜单项,对齐 Android `Constants.menuList`。
|
||||
static let allItems: [HomeMenuItem] = [
|
||||
HomeMenuItem(uri: "space_settings", title: "空间设置", iconName: "home_menu_space"),
|
||||
@@ -45,6 +47,7 @@ enum HomeMenuCatalog {
|
||||
/// 将服务端权限映射为可展示菜单,保持服务端顺序。
|
||||
static func visibleMenus(from permissions: [HomePermissionItem]) -> [HomeMenuItem] {
|
||||
permissions.compactMap { permission in
|
||||
guard isCustomizable(uri: permission.uri) else { return nil }
|
||||
guard let catalog = allItems.first(where: { $0.uri == permission.uri }) else { return nil }
|
||||
return HomeMenuItem(
|
||||
uri: catalog.uri,
|
||||
@@ -59,4 +62,9 @@ enum HomeMenuCatalog {
|
||||
static func item(for uri: String) -> HomeMenuItem? {
|
||||
allItems.first { $0.uri == uri }
|
||||
}
|
||||
|
||||
/// 判断入口是否允许出现在首页常用应用及其自定义页面中。
|
||||
static func isCustomizable(uri: String) -> Bool {
|
||||
!fixedHomeEntryURIs.contains(uri)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,21 +16,24 @@ enum HomeMenuIconFactory {
|
||||
|
||||
private static let imageCache = NSCache<NSString, UIImage>()
|
||||
|
||||
/// 优先加载 Assets 资源图;否则加载 SF Symbol,并统一渲染到固定画布。
|
||||
/// 优先加载 Assets 资源图并归一化画布;SF Symbol 保留系统符号表示与分层渲染。
|
||||
static func image(named iconName: String) -> UIImage? {
|
||||
let cacheKey = iconName as NSString
|
||||
if let cachedImage = imageCache.object(forKey: cacheKey) {
|
||||
return cachedImage
|
||||
}
|
||||
|
||||
guard let sourceImage = UIImage(named: iconName)
|
||||
?? UIImage(systemName: iconName, withConfiguration: symbolConfiguration) else {
|
||||
return nil
|
||||
let image: UIImage?
|
||||
if let assetImage = UIImage(named: iconName) {
|
||||
image = renderOnFixedCanvas(assetImage)
|
||||
} else {
|
||||
image = UIImage(systemName: iconName, withConfiguration: symbolConfiguration)
|
||||
}
|
||||
|
||||
let normalizedImage = renderOnFixedCanvas(sourceImage)
|
||||
imageCache.setObject(normalizedImage, forKey: cacheKey)
|
||||
return normalizedImage
|
||||
if let image {
|
||||
imageCache.setObject(image, forKey: cacheKey)
|
||||
}
|
||||
return image
|
||||
}
|
||||
|
||||
private static func renderOnFixedCanvas(_ image: UIImage) -> UIImage {
|
||||
|
||||
@@ -48,6 +48,7 @@ final class AllFunctionsViewModel {
|
||||
|
||||
/// 将菜单加入常用应用。
|
||||
func addToCommon(_ menu: HomeMenuItem) {
|
||||
guard HomeMenuCatalog.isCustomizable(uri: menu.uri) else { return }
|
||||
guard !commonMenus.contains(where: { $0.uri == menu.uri }) else { return }
|
||||
commonMenus.append(menu)
|
||||
moreMenus.removeAll { $0.uri == menu.uri }
|
||||
|
||||
@@ -25,6 +25,7 @@ final class HomeViewModel {
|
||||
private(set) var reportTimeText = ""
|
||||
private(set) var isLocationReporting = false
|
||||
private(set) var needsPermissionReload = false
|
||||
private(set) var unreadMessageCount = 0
|
||||
|
||||
var onStateChange: (() -> Void)?
|
||||
var onShowMessage: ((String) -> Void)?
|
||||
@@ -65,6 +66,7 @@ final class HomeViewModel {
|
||||
var isOnline: Bool { locationStateStore.isOnline }
|
||||
var countdownDisplayText: String { locationStateStore.countdownDisplayText }
|
||||
var reminderMinutes: Int { locationStateStore.reminderMinutes }
|
||||
var hasUnreadMessages: Bool { unreadMessageCount > 0 }
|
||||
|
||||
/// 首次进入首页时加载权限并恢复在线状态。
|
||||
func initialize(api: HomeAPI) async {
|
||||
@@ -76,6 +78,28 @@ final class HomeViewModel {
|
||||
/// 账号切换后标记需要重新拉取权限。
|
||||
func markNeedsPermissionReload() {
|
||||
needsPermissionReload = true
|
||||
unreadMessageCount = 0
|
||||
}
|
||||
|
||||
/// 查询未读消息总数,成功时更新首页状态并返回 `true`。
|
||||
func refreshUnreadMessageStatus(api: any MessageCenterServing) async -> Bool {
|
||||
do {
|
||||
let response = try await api.unreadCount()
|
||||
unreadMessageCount = response.unreadCount
|
||||
notifyStateChange()
|
||||
return true
|
||||
} catch is CancellationError {
|
||||
return false
|
||||
} catch {
|
||||
// 查询失败时保留上一次状态,避免网络抖动导致红点错误消失。
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/// 使用已知的最新未读消息数量立即刷新首页状态。
|
||||
func updateUnreadMessageCount(_ count: Int) {
|
||||
unreadMessageCount = max(count, 0)
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 查询待审核记录并决定进入申请页或审核状态页。
|
||||
|
||||
@@ -20,6 +20,7 @@ enum AMapBootstrap {
|
||||
defer { lock.unlock() }
|
||||
guard !isConfigured else { return true }
|
||||
|
||||
#if !targetEnvironment(simulator)
|
||||
AMapLocationManager.updatePrivacyShow(.didShow, privacyInfo: .didContain)
|
||||
AMapLocationManager.updatePrivacyAgree(.didAgree)
|
||||
MAMapView.updatePrivacyShow(.didShow, privacyInfo: .didContain)
|
||||
@@ -29,6 +30,7 @@ enum AMapBootstrap {
|
||||
|
||||
AMapServices.shared().apiKey = AMapConfig.apiKey
|
||||
AMapServices.shared().enableHTTPS = true
|
||||
#endif
|
||||
isConfigured = true
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -3,16 +3,28 @@
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
import Foundation
|
||||
|
||||
/// 高德逆地理编码封装,对齐 Android `GeocodeSearch`。
|
||||
/// 地址逆地理编码能力,供平台定位实现注入。
|
||||
@MainActor
|
||||
final class LocationGeocoder: NSObject, AMapSearchDelegate {
|
||||
protocol LocationAddressGeocoding: AnyObject {
|
||||
/// 将坐标解析为可读地址,失败时返回空字符串。
|
||||
func reverseGeocode(latitude: Double, longitude: Double) async -> String
|
||||
}
|
||||
|
||||
/// 平台逆地理编码封装;真机使用高德,模拟器使用 Core Location。
|
||||
@MainActor
|
||||
final class LocationGeocoder: NSObject, LocationAddressGeocoding {
|
||||
|
||||
nonisolated static let shared = LocationGeocoder()
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
private let geocoder = CLGeocoder()
|
||||
#else
|
||||
private var searchAPI: AMapSearchAPI?
|
||||
private var continuation: CheckedContinuation<String, Never>?
|
||||
#endif
|
||||
|
||||
nonisolated private override init() {
|
||||
super.init()
|
||||
@@ -20,6 +32,26 @@ final class LocationGeocoder: NSObject, AMapSearchDelegate {
|
||||
|
||||
/// 将坐标解析为可读地址。
|
||||
func reverseGeocode(latitude: Double, longitude: Double) async -> String {
|
||||
#if targetEnvironment(simulator)
|
||||
let location = CLLocation(latitude: latitude, longitude: longitude)
|
||||
guard let placemark = try? await geocoder.reverseGeocodeLocation(location).first else {
|
||||
return ""
|
||||
}
|
||||
if let name = placemark.name?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!name.isEmpty {
|
||||
return name
|
||||
}
|
||||
return [
|
||||
placemark.administrativeArea,
|
||||
placemark.locality,
|
||||
placemark.subLocality,
|
||||
placemark.thoroughfare,
|
||||
placemark.subThoroughfare,
|
||||
]
|
||||
.compactMap { $0?.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||
.filter { !$0.isEmpty }
|
||||
.joined()
|
||||
#else
|
||||
do {
|
||||
try AMapBootstrap.requireConfigured()
|
||||
} catch {
|
||||
@@ -40,8 +72,10 @@ final class LocationGeocoder: NSObject, AMapSearchDelegate {
|
||||
request.radius = 200
|
||||
api.aMapReGoecodeSearch(request)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !targetEnvironment(simulator)
|
||||
func onReGeocodeSearchDone(_ request: AMapReGeocodeSearchRequest!, response: AMapReGeocodeSearchResponse!) {
|
||||
let address = response?.regeocode?.formattedAddress ?? ""
|
||||
continuation?.resume(returning: address)
|
||||
@@ -62,4 +96,9 @@ final class LocationGeocoder: NSObject, AMapSearchDelegate {
|
||||
searchAPI = api
|
||||
return api
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !targetEnvironment(simulator)
|
||||
extension LocationGeocoder: AMapSearchDelegate {}
|
||||
#endif
|
||||
|
||||
@@ -6,7 +6,165 @@
|
||||
import CoreLocation
|
||||
import Foundation
|
||||
|
||||
/// 统一定位服务,封装高德 `AMapLocationManager`,对齐 Android `LocationProvider`。
|
||||
/// Core Location 管理器最小接口,便于模拟器定位实现注入测试替身。
|
||||
@MainActor
|
||||
protocol CoreLocationManaging: AnyObject {
|
||||
var authorizationStatus: CLAuthorizationStatus { get }
|
||||
var desiredAccuracy: CLLocationAccuracy { get set }
|
||||
var delegate: CLLocationManagerDelegate? { get set }
|
||||
|
||||
/// 请求使用期间定位权限。
|
||||
func requestWhenInUseAuthorization()
|
||||
/// 请求一次当前位置。
|
||||
func requestLocation()
|
||||
}
|
||||
|
||||
extension CLLocationManager: CoreLocationManaging {}
|
||||
|
||||
/// Core Location 定位实现,供模拟器运行并支持依赖注入测试。
|
||||
@MainActor
|
||||
final class CoreLocationProvider: NSObject, LocationProviding, CLLocationManagerDelegate {
|
||||
|
||||
nonisolated static let shared = CoreLocationProvider()
|
||||
|
||||
private var appStore: AppStore = .shared
|
||||
private var manager: any CoreLocationManaging = CLLocationManager()
|
||||
private var geocoder: any LocationAddressGeocoding = LocationGeocoder.shared
|
||||
private var permissionContinuation: CheckedContinuation<Void, Error>?
|
||||
private var locationContinuation: CheckedContinuation<CLLocationCoordinate2D, Error>?
|
||||
|
||||
nonisolated override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
/// 使用可控依赖创建 Core Location 定位服务。
|
||||
init(
|
||||
appStore: AppStore,
|
||||
manager: any CoreLocationManaging,
|
||||
geocoder: any LocationAddressGeocoding
|
||||
) {
|
||||
self.appStore = appStore
|
||||
self.manager = manager
|
||||
self.geocoder = geocoder
|
||||
super.init()
|
||||
manager.delegate = self
|
||||
}
|
||||
|
||||
/// 请求坐标及逆地理编码地址。
|
||||
func requestSnapshot(
|
||||
desiredAccuracy: CLLocationAccuracy = kCLLocationAccuracyBest
|
||||
) async throws -> HomeLocationSnapshot {
|
||||
let coordinate = try await requestCoordinate(desiredAccuracy: desiredAccuracy)
|
||||
let address = await geocoder.reverseGeocode(
|
||||
latitude: coordinate.latitude,
|
||||
longitude: coordinate.longitude
|
||||
)
|
||||
return HomeLocationSnapshot(
|
||||
latitude: coordinate.latitude,
|
||||
longitude: coordinate.longitude,
|
||||
address: address
|
||||
)
|
||||
}
|
||||
|
||||
/// 请求一次当前位置坐标。
|
||||
func requestCoordinate(
|
||||
desiredAccuracy: CLLocationAccuracy
|
||||
) async throws -> CLLocationCoordinate2D {
|
||||
try requirePrivacyAgreement()
|
||||
try await ensureLocationPermission()
|
||||
guard locationContinuation == nil else {
|
||||
throw LocationProviderError.locationFailed
|
||||
}
|
||||
|
||||
manager.delegate = self
|
||||
manager.desiredAccuracy = desiredAccuracy
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
locationContinuation = continuation
|
||||
manager.requestLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/// 使用系统服务将坐标解析为地址。
|
||||
func reverseGeocode(latitude: Double, longitude: Double) async -> String {
|
||||
guard appStore.session.privacyAgreementAccepted else { return "" }
|
||||
return await geocoder.reverseGeocode(latitude: latitude, longitude: longitude)
|
||||
}
|
||||
|
||||
/// 请求系统定位权限,等待用户完成首次授权。
|
||||
func ensureLocationPermission() async throws {
|
||||
manager.delegate = self
|
||||
switch manager.authorizationStatus {
|
||||
case .authorizedAlways, .authorizedWhenInUse:
|
||||
return
|
||||
case .denied, .restricted:
|
||||
throw LocationProviderError.permissionDenied
|
||||
case .notDetermined:
|
||||
guard permissionContinuation == nil else {
|
||||
throw LocationProviderError.locationFailed
|
||||
}
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
permissionContinuation = continuation
|
||||
manager.requestWhenInUseAuthorization()
|
||||
}
|
||||
@unknown default:
|
||||
throw LocationProviderError.permissionDenied
|
||||
}
|
||||
}
|
||||
|
||||
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
||||
resolvePermission(status: self.manager.authorizationStatus)
|
||||
}
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||
guard let continuation = locationContinuation else { return }
|
||||
locationContinuation = nil
|
||||
guard let coordinate = locations.last?.coordinate,
|
||||
CLLocationCoordinate2DIsValid(coordinate),
|
||||
coordinate.latitude != 0 || coordinate.longitude != 0 else {
|
||||
continuation.resume(throwing: LocationProviderError.locationFailed)
|
||||
return
|
||||
}
|
||||
continuation.resume(returning: coordinate)
|
||||
}
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
||||
guard let continuation = locationContinuation else { return }
|
||||
locationContinuation = nil
|
||||
continuation.resume(throwing: LocationProviderError.locationFailed)
|
||||
}
|
||||
|
||||
private func requirePrivacyAgreement() throws {
|
||||
guard appStore.session.privacyAgreementAccepted else {
|
||||
throw LocationProviderError.privacyNotAccepted
|
||||
}
|
||||
}
|
||||
|
||||
private func resolvePermission(status: CLAuthorizationStatus) {
|
||||
guard let continuation = permissionContinuation else { return }
|
||||
switch status {
|
||||
case .authorizedAlways, .authorizedWhenInUse:
|
||||
permissionContinuation = nil
|
||||
continuation.resume()
|
||||
case .denied, .restricted:
|
||||
permissionContinuation = nil
|
||||
continuation.resume(throwing: LocationProviderError.permissionDenied)
|
||||
case .notDetermined:
|
||||
break
|
||||
@unknown default:
|
||||
permissionContinuation = nil
|
||||
continuation.resume(throwing: LocationProviderError.permissionDenied)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
|
||||
/// 模拟器默认定位服务,使用 Core Location 与系统逆地理编码。
|
||||
typealias LocationProvider = CoreLocationProvider
|
||||
|
||||
#else
|
||||
|
||||
/// 真机统一定位服务,封装高德 `AMapLocationManager`,对齐 Android `LocationProvider`。
|
||||
@MainActor
|
||||
final class LocationProvider: NSObject, LocationProviding {
|
||||
|
||||
@@ -19,7 +177,7 @@ final class LocationProvider: NSObject, LocationProviding {
|
||||
super.init()
|
||||
}
|
||||
|
||||
@MainActor
|
||||
/// 请求坐标及高德逆地理编码地址。
|
||||
func requestSnapshot(desiredAccuracy: CLLocationAccuracy = kCLLocationAccuracyBest) async throws -> HomeLocationSnapshot {
|
||||
try AMapBootstrap.requireConfigured()
|
||||
try await ensureLocationPermission()
|
||||
@@ -51,7 +209,7 @@ final class LocationProvider: NSObject, LocationProviding {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
/// 请求一次当前位置坐标。
|
||||
func requestCoordinate(desiredAccuracy: CLLocationAccuracy) async throws -> CLLocationCoordinate2D {
|
||||
try AMapBootstrap.requireConfigured()
|
||||
try await ensureLocationPermission()
|
||||
@@ -78,7 +236,7 @@ final class LocationProvider: NSObject, LocationProviding {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
/// 使用高德服务将坐标解析为地址。
|
||||
func reverseGeocode(latitude: Double, longitude: Double) async -> String {
|
||||
await geocoder.reverseGeocode(latitude: latitude, longitude: longitude)
|
||||
}
|
||||
@@ -110,3 +268,5 @@ final class LocationProvider: NSObject, LocationProviding {
|
||||
return manager
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -16,20 +16,25 @@ struct HomeLocationSnapshot: Sendable, Equatable {
|
||||
/// 定位服务协议,便于 ViewModel 注入与单元测试 mock。
|
||||
protocol LocationProviding: Sendable {
|
||||
/// 按指定精度请求当前坐标与地址。
|
||||
@MainActor
|
||||
func requestSnapshot(desiredAccuracy: CLLocationAccuracy) async throws -> HomeLocationSnapshot
|
||||
/// 按指定精度请求当前坐标。
|
||||
@MainActor
|
||||
func requestCoordinate(desiredAccuracy: CLLocationAccuracy) async throws -> CLLocationCoordinate2D
|
||||
/// 逆地理编码。
|
||||
@MainActor
|
||||
func reverseGeocode(latitude: Double, longitude: Double) async -> String
|
||||
}
|
||||
|
||||
extension LocationProviding {
|
||||
/// 请求当前坐标与地址,默认使用最高精度以保持既有业务行为。
|
||||
@MainActor
|
||||
func requestSnapshot() async throws -> HomeLocationSnapshot {
|
||||
try await requestSnapshot(desiredAccuracy: kCLLocationAccuracyBest)
|
||||
}
|
||||
|
||||
/// 请求当前坐标,默认使用最高精度以保持既有业务行为。
|
||||
@MainActor
|
||||
func requestCoordinate() async throws -> CLLocationCoordinate2D {
|
||||
try await requestCoordinate(desiredAccuracy: kCLLocationAccuracyBest)
|
||||
}
|
||||
|
||||
@@ -11,8 +11,14 @@ protocol MessageCenterServing {
|
||||
/// 拉取消息列表。
|
||||
func list(lastId: Int, limit: Int, unread: Int) async throws -> MessageListResponse
|
||||
|
||||
/// 标记消息已读。
|
||||
func markAsRead(messageId: Int) async throws
|
||||
/// 获取当前账号未读消息总数。
|
||||
func unreadCount() async throws -> MessageUnreadCountResponse
|
||||
|
||||
/// 标记消息已读并返回最新未读数量。
|
||||
func markAsRead(messageId: Int) async throws -> MessageUnreadCountResponse
|
||||
|
||||
/// 将当前账号全部消息标记已读。
|
||||
func markAllAsRead() async throws -> MessageReadAllResponse
|
||||
|
||||
/// 删除消息。
|
||||
func delete(messageId: Int) async throws
|
||||
@@ -43,9 +49,16 @@ final class MessageCenterAPI: MessageCenterServing {
|
||||
)
|
||||
}
|
||||
|
||||
/// GET /api/app/msg/unread-count
|
||||
func unreadCount() async throws -> MessageUnreadCountResponse {
|
||||
try await client.send(
|
||||
APIRequest(method: .get, path: "/api/app/msg/unread-count")
|
||||
)
|
||||
}
|
||||
|
||||
/// POST /api/app/msg/read
|
||||
func markAsRead(messageId: Int) async throws {
|
||||
let _: EmptyPayload = try await client.send(
|
||||
func markAsRead(messageId: Int) async throws -> MessageUnreadCountResponse {
|
||||
try await client.send(
|
||||
APIRequest(
|
||||
method: .post,
|
||||
path: "/api/app/msg/read",
|
||||
@@ -54,6 +67,13 @@ final class MessageCenterAPI: MessageCenterServing {
|
||||
)
|
||||
}
|
||||
|
||||
/// POST /api/app/msg/read-all
|
||||
func markAllAsRead() async throws -> MessageReadAllResponse {
|
||||
try await client.send(
|
||||
APIRequest(method: .post, path: "/api/app/msg/read-all")
|
||||
)
|
||||
}
|
||||
|
||||
/// POST /api/app/msg/delete
|
||||
func delete(messageId: Int) async throws {
|
||||
let _: EmptyPayload = try await client.send(
|
||||
|
||||
@@ -32,6 +32,46 @@ enum MessageJSONValue: Decodable, Hashable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
/// 消息未读数量响应,用于同步首页红点和桌面图标角标。
|
||||
struct MessageUnreadCountResponse: Decodable, Equatable, Sendable {
|
||||
let unreadCount: Int
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case unreadCount = "unread_count"
|
||||
}
|
||||
|
||||
init(unreadCount: Int = 0) {
|
||||
self.unreadCount = max(unreadCount, 0)
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
unreadCount = max(try container.decodeLossyInt(forKey: .unreadCount) ?? 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// 全部标记已读响应,包含本次更新数量和操作后的未读数量。
|
||||
struct MessageReadAllResponse: Decodable, Equatable, Sendable {
|
||||
let updatedCount: Int
|
||||
let unreadCount: Int
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case updatedCount = "updated_count"
|
||||
case unreadCount = "unread_count"
|
||||
}
|
||||
|
||||
init(updatedCount: Int = 0, unreadCount: Int = 0) {
|
||||
self.updatedCount = max(updatedCount, 0)
|
||||
self.unreadCount = max(unreadCount, 0)
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
updatedCount = max(try container.decodeLossyInt(forKey: .updatedCount) ?? 0, 0)
|
||||
unreadCount = max(try container.decodeLossyInt(forKey: .unreadCount) ?? 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// 消息中心列表响应,对齐 Android `MsgResponse`。
|
||||
struct MessageListResponse: Decodable, Equatable, Sendable {
|
||||
let hasMore: Bool
|
||||
|
||||
@@ -11,11 +11,13 @@ final class MessageCenterViewModel {
|
||||
private(set) var isLoading = false
|
||||
private(set) var isRefreshing = false
|
||||
private(set) var isLoadingMore = false
|
||||
private(set) var isMarkingAllAsRead = false
|
||||
private(set) var canLoadMore = false
|
||||
|
||||
var onStateChange: (() -> Void)?
|
||||
var onShowMessage: ((String) -> Void)?
|
||||
var onOpenDetail: ((MessageItem) -> Void)?
|
||||
var onUnreadMessageCountChange: ((Int) -> Void)?
|
||||
|
||||
private let pageSize = 20
|
||||
private var lastId = 0
|
||||
@@ -55,9 +57,10 @@ final class MessageCenterViewModel {
|
||||
}
|
||||
|
||||
do {
|
||||
try await api.markAsRead(messageId: id)
|
||||
let response = try await api.markAsRead(messageId: id)
|
||||
let readItem = item.markedRead()
|
||||
items = items.map { $0.id == id ? readItem : $0 }
|
||||
notifyUnreadMessageCountChanged(response.unreadCount)
|
||||
onOpenDetail?(readItem)
|
||||
} catch is CancellationError {
|
||||
return
|
||||
@@ -66,6 +69,30 @@ final class MessageCenterViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
/// 将当前账号的全部消息标记已读,成功时返回服务端最新统计。
|
||||
func markAllAsRead(api: any MessageCenterServing) async -> MessageReadAllResponse? {
|
||||
guard !isLoading, !isMarkingAllAsRead else { return nil }
|
||||
isMarkingAllAsRead = true
|
||||
notifyStateChange()
|
||||
defer {
|
||||
isMarkingAllAsRead = false
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
do {
|
||||
let response = try await api.markAllAsRead()
|
||||
items = items.map { $0.isRead ? $0 : $0.markedRead() }
|
||||
notifyUnreadMessageCountChanged(response.unreadCount)
|
||||
onShowMessage?(response.updatedCount > 0 ? "已全部标记为已读" : "暂无未读消息")
|
||||
return response
|
||||
} catch is CancellationError {
|
||||
return nil
|
||||
} catch {
|
||||
onShowMessage?("全部标记已读失败,请稍后重试")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除后从本地列表移除消息。
|
||||
func removeMessage(id: Int) {
|
||||
items.removeAll { $0.id == id }
|
||||
@@ -110,6 +137,16 @@ final class MessageCenterViewModel {
|
||||
private func notifyStateChange() {
|
||||
onStateChange?()
|
||||
}
|
||||
|
||||
private func notifyUnreadMessageCountChanged(_ count: Int) {
|
||||
let normalizedCount = max(count, 0)
|
||||
onUnreadMessageCountChange?(normalizedCount)
|
||||
NotificationCenter.default.post(
|
||||
name: NotificationName.unreadMessageCountDidChange,
|
||||
object: nil,
|
||||
userInfo: [NotificationUserInfoKey.unreadCount: normalizedCount]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 消息详情页 ViewModel,负责删除当前消息。
|
||||
|
||||
@@ -293,6 +293,28 @@ final class OrderAPI {
|
||||
)
|
||||
}
|
||||
|
||||
/// 获取指定获客员的分成比例修改记录。
|
||||
func saleUserCommissionRateLogs(
|
||||
saleUserId: Int,
|
||||
page: Int = 1,
|
||||
pageSize: Int = 10
|
||||
) async throws -> CommissionRateLogListResponse {
|
||||
guard saleUserId > 0 else {
|
||||
throw APIError.networkFailed("获客员信息无效")
|
||||
}
|
||||
return try await client.send(
|
||||
APIRequest(
|
||||
method: .get,
|
||||
path: "/api/yf-handset-app/photog/sale-user/commission-rate/logs",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "sale_user_id", value: String(saleUserId)),
|
||||
URLQueryItem(name: "page", value: String(max(1, page))),
|
||||
URLQueryItem(name: "page_size", value: String(min(max(1, pageSize), 50))),
|
||||
]
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func shootHistory(orderNumber: String) async throws -> MultiTravelShootHistoryResponse {
|
||||
try await client.send(
|
||||
APIRequest(
|
||||
|
||||
@@ -875,6 +875,93 @@ struct CooperativeSalerEntity: Decodable, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
/// 获客员分成比例修改记录分页响应。
|
||||
struct CommissionRateLogListResponse: Decodable, Equatable {
|
||||
let list: [CommissionRateLogEntity]
|
||||
let total: Int
|
||||
let page: Int
|
||||
let pageSize: Int
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case list
|
||||
case total
|
||||
case page
|
||||
case pageSize = "page_size"
|
||||
}
|
||||
}
|
||||
|
||||
/// 单条获客员分成比例修改记录。
|
||||
struct CommissionRateLogEntity: Decodable, Equatable, Hashable {
|
||||
let id: Int
|
||||
let bindingId: Int
|
||||
let saleUserId: Int
|
||||
let storeUserId: Int
|
||||
let beforeRate: Int
|
||||
let beforeRateLabel: String
|
||||
let afterRate: Int
|
||||
let afterRateLabel: String
|
||||
let operatorType: String
|
||||
let operatorId: Int
|
||||
let operatorName: String
|
||||
let remark: String
|
||||
let createdAt: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case bindingId = "binding_id"
|
||||
case saleUserId = "sale_user_id"
|
||||
case storeUserId = "store_user_id"
|
||||
case beforeRate = "before_rate"
|
||||
case beforeRateLabel = "before_rate_label"
|
||||
case afterRate = "after_rate"
|
||||
case afterRateLabel = "after_rate_label"
|
||||
case operatorType = "operator_type"
|
||||
case operatorId = "operator_id"
|
||||
case operatorName = "operator_name"
|
||||
case remark
|
||||
case createdAt = "created_at"
|
||||
}
|
||||
|
||||
/// 修改前比例展示文本,优先使用服务端 label。
|
||||
var displayBeforeRate: String {
|
||||
let label = beforeRateLabel.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return label.isEmpty ? "\(beforeRate)%" : label
|
||||
}
|
||||
|
||||
/// 修改后比例展示文本,优先使用服务端 label。
|
||||
var displayAfterRate: String {
|
||||
let label = afterRateLabel.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return label.isEmpty ? "\(afterRate)%" : label
|
||||
}
|
||||
|
||||
/// 修改时间展示文本。
|
||||
var metadataLine: String {
|
||||
formattedCreatedAt
|
||||
}
|
||||
|
||||
/// 去除首尾空白后的备注;空备注不展示。
|
||||
var displayRemark: String? {
|
||||
let value = remark.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return value.isEmpty ? nil : value
|
||||
}
|
||||
|
||||
private var formattedCreatedAt: String {
|
||||
let value = createdAt.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !value.isEmpty else { return "—" }
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter.timeZone = TimeZone(identifier: "Asia/Shanghai")
|
||||
for format in ["yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm"] {
|
||||
formatter.dateFormat = format
|
||||
if let date = formatter.date(from: value) {
|
||||
formatter.dateFormat = "yyyy-MM-dd HH:mm"
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
struct ReferralOrderListResponse: Decodable {
|
||||
let total: Int
|
||||
let list: [ReferralOrderEntity]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
import UIKit
|
||||
|
||||
/// 主 Tab 中间扫码按钮结果处理,对齐 Android `MainTabViewModel.signIn`。
|
||||
|
||||
@@ -25,6 +25,17 @@ final class PaymentAPI {
|
||||
)
|
||||
}
|
||||
|
||||
/// 拉取当前景区收款页的背景、Logo 与标题配置。
|
||||
func payPageConfig(scenicId: Int) async throws -> PayPageConfigResponse {
|
||||
try await client.send(
|
||||
APIRequest(
|
||||
method: .get,
|
||||
path: "/api/yf-handset-app/photog/pay-page-config",
|
||||
queryItems: [URLQueryItem(name: "scenic_id", value: String(scenicId))]
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// 拉取近 7 天收款记录。
|
||||
func collectionRecord(scenicId: Int) async throws -> RepaymentCollectionRecordResponse {
|
||||
try await client.send(
|
||||
|
||||
@@ -5,6 +5,83 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 收款页展示配置接口响应。
|
||||
struct PayPageConfigResponse: Decodable, Sendable, Equatable {
|
||||
let background: String
|
||||
let logo: String
|
||||
let title: String
|
||||
let subtitle: String
|
||||
|
||||
init(background: String, logo: String, title: String, subtitle: String) {
|
||||
self.background = background
|
||||
self.logo = logo
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
}
|
||||
}
|
||||
|
||||
/// 收款页最终可展示并可持久化的品牌配置。
|
||||
struct PayPageConfig: Codable, Sendable, Equatable {
|
||||
let background: String
|
||||
let logo: String
|
||||
let title: String
|
||||
let subtitle: String
|
||||
|
||||
/// 那拉提景区随包默认配置。
|
||||
static let nalatiDefault = PayPageConfig(
|
||||
background: "",
|
||||
logo: "",
|
||||
title: "那拉提旅拍管理收费平台",
|
||||
subtitle: "那拉提景区"
|
||||
)
|
||||
|
||||
/// 将服务端非空有效字段合并到当前可用配置。
|
||||
func merging(_ response: PayPageConfigResponse) -> PayPageConfig {
|
||||
PayPageConfig(
|
||||
background: Self.validRemoteImageURL(response.background) ?? background,
|
||||
logo: Self.validRemoteImageURL(response.logo) ?? logo,
|
||||
title: response.title.trimmedNonEmpty ?? title,
|
||||
subtitle: response.subtitle.trimmedNonEmpty ?? subtitle
|
||||
)
|
||||
}
|
||||
|
||||
private static func validRemoteImageURL(_ value: String) -> String? {
|
||||
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard let url = URL(string: trimmed),
|
||||
url.scheme?.lowercased() == "https",
|
||||
url.host != nil else {
|
||||
return nil
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
}
|
||||
|
||||
/// 收款页品牌化景区判定策略。
|
||||
enum PayPageBrandingPolicy {
|
||||
/// 那拉提景区正式 ID。
|
||||
static let nalatiScenicId = 128
|
||||
|
||||
/// 判断当前景区是否启用品牌收款页。
|
||||
static func isNalati(scenicId: Int) -> Bool {
|
||||
scenicId == nalatiScenicId
|
||||
}
|
||||
}
|
||||
|
||||
/// 收款页当前账号信息的展示文本格式化规则。
|
||||
enum PaymentAccountDisplayFormatter {
|
||||
/// 解析当前登录摄影师的真实姓名,空值显示占位符。
|
||||
static func photographerName(realName: String) -> String {
|
||||
realName.trimmedNonEmpty ?? "-"
|
||||
}
|
||||
|
||||
/// 去除空值与重复项后,合并当前账号的全部店铺名称。
|
||||
static func storeNames(_ names: [String]) -> String {
|
||||
var seen = Set<String>()
|
||||
let resolved = names.compactMap(\.trimmedNonEmpty).filter { seen.insert($0).inserted }
|
||||
return resolved.isEmpty ? "-" : resolved.joined(separator: "、")
|
||||
}
|
||||
}
|
||||
|
||||
/// 摄影师收款码响应,包含静态与动态支付 URL。
|
||||
struct PayCodeResponse: Decodable, Sendable, Equatable {
|
||||
let staticPayURL: String
|
||||
@@ -101,3 +178,10 @@ struct PaymentMerchantInfo: Sendable, Equatable {
|
||||
company: "扬州元智享网络科技有限公司"
|
||||
)
|
||||
}
|
||||
|
||||
private extension String {
|
||||
var trimmedNonEmpty: String? {
|
||||
let value = trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return value.isEmpty ? nil : value
|
||||
}
|
||||
}
|
||||
|
||||