From 21368835367262d6ace800a345b9c97aa951aa94 Mon Sep 17 00:00:00 2001 From: hext Date: Sun, 27 Feb 2022 23:02:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=BE=AE=E4=BF=A1=E7=99=BB?= =?UTF-8?q?=E5=BD=95,=20=E4=BF=AE=E5=A4=8D=E4=B8=80=E4=BA=9Bbug:=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=BE=AE=E4=BF=A1=E7=99=BB=E5=BD=95,=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=90=8C=E6=97=B6=E7=BB=91=E5=AE=9A=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E5=92=8C=E8=8B=B9=E6=9E=9C=E8=B4=A6=E5=8F=B7;=20?= =?UTF-8?q?=E8=A7=A3=E5=86=B3markdown=E6=A0=87=E9=A2=98=E6=96=87=E5=AD=97?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E4=B8=8D=E5=85=A8=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?;=20markdown=E4=B8=AD=E7=9A=84=E9=93=BE=E6=8E=A5=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=86=85=E5=B5=8C=E6=B5=8F=E8=A7=88=E5=99=A8=E6=89=93?= =?UTF-8?q?=E5=BC=80;=20=E5=A2=9E=E5=8A=A0=E6=98=AF=E5=90=A6=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E5=86=85=E7=BD=AE=E6=B5=8F=E8=A7=88=E5=99=A8=E7=9A=84?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=A1=B9;=20=E6=94=B9=E5=90=8D=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E6=A1=86,=20=E5=A4=B1=E5=8E=BB=E7=84=A6=E7=82=B9?= =?UTF-8?q?=E7=9A=84=E6=97=B6=E5=80=99=E4=B9=9F=E4=BF=9D=E5=AD=98;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/PushDeer-iOS/Podfile | 25 +- ios/PushDeer-iOS/Podfile.lock | 10 +- .../PushDeer.xcodeproj/project.pbxproj | 40 +- .../xcshareddata/WorkspaceSettings.xcsettings | 2 + .../xcshareddata/swiftpm/Package.resolved | 25 +- ios/PushDeer-iOS/PushDeer/AppDelegate.swift | 9 +- .../weixinBgColor.colorset/Contents.json | 38 ++ .../weixinFgColor.colorset/Contents.json | 38 ++ .../weixin-login.imageset/Contents.json | 21 + .../weixin-login.imageset/微信LOGO_轮廓.png | Bin 0 -> 3078 bytes .../PushDeer/Common/SafariView.swift | 24 + ios/PushDeer-iOS/PushDeer/Env.swift | 5 + ios/PushDeer-iOS/PushDeer/Info.plist | 18 + ios/PushDeer-iOS/PushDeer/Model/Result.swift | 6 +- .../PushDeer/PushDeer-Bridging-Header.h | 7 + .../PushDeer/PushDeer.entitlements | 1 + ios/PushDeer-iOS/PushDeer/PushDeerApp.swift | 12 + .../PushDeer/Service/AppState.swift | 12 + .../PushDeer/Service/HttpRequest.swift | 23 +- .../PushDeer/Service/PushDeerApi.swift | 18 + .../PushDeer/Service/WXDelegate.swift | 57 +++ .../PushDeer/View/Common/EditableText.swift | 13 +- .../PushDeer/View/DeviceItemView.swift | 8 +- .../PushDeer/View/KeyItemView.swift | 6 +- .../PushDeer/View/LoginView.swift | 37 ++ .../PushDeer/View/MessageItemView.swift | 69 ++- .../PushDeer/View/SettingsView.swift | 141 ++++++ .../PushDeer/en.lproj/Localizable.strings | 31 +- .../remove_unsupported_libraries.rb | 447 ++++++++++++++++++ 29 files changed, 1100 insertions(+), 43 deletions(-) create mode 100644 ios/PushDeer-iOS/PushDeer/Assets.xcassets/Colors/weixinBgColor.colorset/Contents.json create mode 100644 ios/PushDeer-iOS/PushDeer/Assets.xcassets/Colors/weixinFgColor.colorset/Contents.json create mode 100644 ios/PushDeer-iOS/PushDeer/Assets.xcassets/Images/weixin-login.imageset/Contents.json create mode 100644 ios/PushDeer-iOS/PushDeer/Assets.xcassets/Images/weixin-login.imageset/微信LOGO_轮廓.png create mode 100644 ios/PushDeer-iOS/PushDeer/Common/SafariView.swift create mode 100644 ios/PushDeer-iOS/PushDeer/PushDeer-Bridging-Header.h create mode 100644 ios/PushDeer-iOS/PushDeer/Service/WXDelegate.swift create mode 100644 ios/PushDeer-iOS/remove_unsupported_libraries.rb diff --git a/ios/PushDeer-iOS/Podfile b/ios/PushDeer-iOS/Podfile index 12e7c04..c3582aa 100644 --- a/ios/PushDeer-iOS/Podfile +++ b/ios/PushDeer-iOS/Podfile @@ -1,6 +1,8 @@ # Uncomment the next line to define a global platform for your project platform :ios, '14.0' +load 'remove_unsupported_libraries.rb' + # Comment the next line if you don't want to use dynamic frameworks use_frameworks! @@ -10,14 +12,14 @@ def commonPods pod 'SDWebImageSwiftUI', '~> 2.0.2' pod 'KRProgressHUD', '~> 3.4.7' pod 'IQKeyboardManagerSwift', '~> 6.5.9' - -# pod 'WoodPeckeriOS', :configurations => ['Debug'] end target 'PushDeer' do commonPods # Pods for PushDeer - + # PushDeer 独享的依赖, Clip 不支持 + pod 'WechatOpenSDK', '~> 1.8.7.1' + pod 'WoodPeckeriOS', :configurations => ['Debug'] end target 'PushDeerClip' do @@ -25,3 +27,20 @@ target 'PushDeerClip' do # Pods for PushDeerClip end + +# define unsupported pods +def unsupported_pods + # macCatalyst 不支持的库 + ['WoodPeckeriOS', 'WechatOpenSDK'] +end + +def supported_pods + # macCatalyst 支持的库 + ['Moya', 'SDWebImageSwiftUI', 'KRProgressHUD', 'IQKeyboardManagerSwift'] +end + +# install all pods except unsupported ones +post_install do |installer| + $verbose = false # remove or set to false to avoid printing + installer.configure_support_catalyst(supported_pods, unsupported_pods) +end diff --git a/ios/PushDeer-iOS/Podfile.lock b/ios/PushDeer-iOS/Podfile.lock index 1a566d8..003a15d 100644 --- a/ios/PushDeer-iOS/Podfile.lock +++ b/ios/PushDeer-iOS/Podfile.lock @@ -13,12 +13,16 @@ PODS: - SDWebImage/Core (5.12.1) - SDWebImageSwiftUI (2.0.2): - SDWebImage (~> 5.10) + - WechatOpenSDK (1.8.7.1) + - WoodPeckeriOS (1.2.93) DEPENDENCIES: - IQKeyboardManagerSwift (~> 6.5.9) - KRProgressHUD (~> 3.4.7) - Moya (~> 15.0) - SDWebImageSwiftUI (~> 2.0.2) + - WechatOpenSDK (~> 1.8.7.1) + - WoodPeckeriOS SPEC REPOS: trunk: @@ -29,6 +33,8 @@ SPEC REPOS: - Moya - SDWebImage - SDWebImageSwiftUI + - WechatOpenSDK + - WoodPeckeriOS SPEC CHECKSUMS: Alamofire: 1c4fb5369c3fe93d2857c780d8bbe09f06f97e7c @@ -38,7 +44,9 @@ SPEC CHECKSUMS: Moya: 138f0573e53411fb3dc17016add0b748dfbd78ee SDWebImage: 4dc3e42d9ec0c1028b960a33ac6b637bb432207b SDWebImageSwiftUI: 8a3923c95108312b03a599ec1498754af55a6819 + WechatOpenSDK: 6a4d1436c15b3b5fe2a0bd383f3046010186da44 + WoodPeckeriOS: 12ec7f38c695e51cd94a476228888dfe85d9d916 -PODFILE CHECKSUM: 3c8d668f811e3c29bb70fc8a24d2ecd9c266ba45 +PODFILE CHECKSUM: 1b349626994062a8291e3db07d3dbf087894c4d2 COCOAPODS: 1.11.2 diff --git a/ios/PushDeer-iOS/PushDeer.xcodeproj/project.pbxproj b/ios/PushDeer-iOS/PushDeer.xcodeproj/project.pbxproj index 6482089..193f003 100644 --- a/ios/PushDeer-iOS/PushDeer.xcodeproj/project.pbxproj +++ b/ios/PushDeer-iOS/PushDeer.xcodeproj/project.pbxproj @@ -77,9 +77,13 @@ 52EB90AE2778AFD60048E0ED /* BaseNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EB90AD2778AFD60048E0ED /* BaseNavigationView.swift */; }; 52EB90B02778D67F0048E0ED /* KeyItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EB90AF2778D67F0048E0ED /* KeyItemView.swift */; }; 52EB90B32778DA4E0048E0ED /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EB90B22778DA4E0048E0ED /* Line.swift */; }; + 52EED71E27C9394D0086A804 /* WXDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EED71D27C9394D0086A804 /* WXDelegate.swift */; }; + 52EED71F27C93B960086A804 /* WXDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EED71D27C9394D0086A804 /* WXDelegate.swift */; }; 52F0243F277737470071D861 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F0243E277737470071D861 /* LoginView.swift */; }; 52F2C223277961D7006F08DC /* SettingsItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F2C222277961D7006F08DC /* SettingsItemView.swift */; }; 52F40D2F277CA05600766C24 /* MessageItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F40D2E277CA05600766C24 /* MessageItemView.swift */; }; + 52FB1FEC27CA9D7300367DE0 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52FB1FEB27CA9D7300367DE0 /* SafariView.swift */; }; + 52FB1FED27CA9D7300367DE0 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52FB1FEB27CA9D7300367DE0 /* SafariView.swift */; }; 52FBA09427874879003308C2 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5292F4FA2776BC7900B9A7BB /* ContentView.swift */; }; 64B0C15E70CCC382B480F76E /* Pods_PushDeer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E380A18349DE4D26071E913E /* Pods_PushDeer.framework */; }; /* End PBXBuildFile section */ @@ -143,6 +147,7 @@ 52B8CF6D277E0B46004CB680 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 52B8CF6F277E0B46004CB680 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52B8CF70277E0B46004CB680 /* PushDeerClip.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PushDeerClip.entitlements; sourceTree = ""; }; + 52BE373227C236DD004AA630 /* PushDeer-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PushDeer-Bridging-Header.h"; sourceTree = ""; }; 52E317D8279305BB000B8BB1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 52E317DB279305BB000B8BB1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 52E317DE279305BB000B8BB1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -153,10 +158,12 @@ 52EB90AD2778AFD60048E0ED /* BaseNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNavigationView.swift; sourceTree = ""; }; 52EB90AF2778D67F0048E0ED /* KeyItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyItemView.swift; sourceTree = ""; }; 52EB90B22778DA4E0048E0ED /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; + 52EED71D27C9394D0086A804 /* WXDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WXDelegate.swift; sourceTree = ""; }; 52F0243C277733CE0071D861 /* PushDeer.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PushDeer.entitlements; sourceTree = ""; }; 52F0243E277737470071D861 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; 52F2C222277961D7006F08DC /* SettingsItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsItemView.swift; sourceTree = ""; }; 52F40D2E277CA05600766C24 /* MessageItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageItemView.swift; sourceTree = ""; }; + 52FB1FEB27CA9D7300367DE0 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; 69F56B2711ED98819D474BE3 /* Pods-PushDeerClip.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PushDeerClip.debug.xcconfig"; path = "Target Support Files/Pods-PushDeerClip/Pods-PushDeerClip.debug.xcconfig"; sourceTree = ""; }; 8B9D658D778AE868A0E052A8 /* Pods-PushDeer.debug-selfhosted.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PushDeer.debug-selfhosted.xcconfig"; path = "Target Support Files/Pods-PushDeer/Pods-PushDeer.debug-selfhosted.xcconfig"; sourceTree = ""; }; 9CC775BE0326BF31C6FACF06 /* Pods-PushDeerClip.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PushDeerClip.release.xcconfig"; path = "Target Support Files/Pods-PushDeerClip/Pods-PushDeerClip.release.xcconfig"; sourceTree = ""; }; @@ -213,6 +220,7 @@ 52450F3A278491F8003652D8 /* AppState.swift */, 52450F412784943F003652D8 /* HttpRequest.swift */, 526A1E722791E03900BA2177 /* Persistence.swift */, + 52EED71D27C9394D0086A804 /* WXDelegate.swift */, ); path = Service; sourceTree = ""; @@ -264,6 +272,7 @@ 52483FC1277ED6D5003A100E /* AppDelegate.swift */, 5292F4F82776BC7900B9A7BB /* PushDeerApp.swift */, 5292F4FA2776BC7900B9A7BB /* ContentView.swift */, + 52BE373227C236DD004AA630 /* PushDeer-Bridging-Header.h */, 5292F4FC2776BC7A00B9A7BB /* Assets.xcassets */, 5292F4FE2776BC7A00B9A7BB /* Preview Content */, ); @@ -285,6 +294,7 @@ 52B8CF5E277DE660004CB680 /* AppleSignInButton.swift */, 52AC5C2727B7FE1D00EEB185 /* ViewExtension.swift */, 52AC5C2A27B8206D00EEB185 /* ListTest.swift */, + 52FB1FEB27CA9D7300367DE0 /* SafariView.swift */, ); path = Common; sourceTree = ""; @@ -410,9 +420,11 @@ TargetAttributes = { 5292F4F42776BC7900B9A7BB = { CreatedOnToolsVersion = 13.2.1; + LastSwiftMigration = 1320; }; 52B8CF63277E0B44004CB680 = { CreatedOnToolsVersion = 13.2.1; + LastSwiftMigration = 1320; }; }; }; @@ -551,6 +563,7 @@ buildActionMask = 2147483647; files = ( 52F0243F277737470071D861 /* LoginView.swift in Sources */, + 52FB1FEC27CA9D7300367DE0 /* SafariView.swift in Sources */, 523150DC2778762B00941EDC /* DeviceItemView.swift in Sources */, 523150D9277875FB00941EDC /* DeletableView.swift in Sources */, 52163EBB277741AC00594190 /* SettingsView.swift in Sources */, @@ -571,6 +584,7 @@ 526A1E7D2792B2A600BA2177 /* MessageModel.swift in Sources */, 52483FC2277ED6D5003A100E /* AppDelegate.swift in Sources */, 52450F3827848243003652D8 /* PushDeerApi.swift in Sources */, + 52EED71E27C9394D0086A804 /* WXDelegate.swift in Sources */, 52EB90AE2778AFD60048E0ED /* BaseNavigationView.swift in Sources */, 524E99E627B3CD0F00292396 /* EndpointView.swift in Sources */, 52EB90AC2778ADF80048E0ED /* CardView.swift in Sources */, @@ -589,6 +603,7 @@ buildActionMask = 2147483647; files = ( 52B8CF82277E0C06004CB680 /* AppleSignInButton.swift in Sources */, + 52FB1FED27CA9D7300367DE0 /* SafariView.swift in Sources */, 52B8CF78277E0BF1004CB680 /* MainView.swift in Sources */, 52B8CF79277E0BFB004CB680 /* DeviceListView.swift in Sources */, 52B8CF84277E0C12004CB680 /* CardView.swift in Sources */, @@ -609,6 +624,7 @@ 526A1E7E2792B2A600BA2177 /* MessageModel.swift in Sources */, 52483FC3277ED6D5003A100E /* AppDelegate.swift in Sources */, 52450F3927848243003652D8 /* PushDeerApi.swift in Sources */, + 52EED71F27C93B960086A804 /* WXDelegate.swift in Sources */, 52B8CF86277E0C12004CB680 /* BaseNavigationView.swift in Sources */, 524E99E727B3CD0F00292396 /* EndpointView.swift in Sources */, 52FBA09427874879003308C2 /* ContentView.swift in Sources */, @@ -733,6 +749,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-SH"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "PushDeer/PushDeer-SelfHosted.entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 4; @@ -760,6 +777,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "PushDeer/PushDeer-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -771,6 +790,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-SH"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "PushDeerClip/PushDeerClip-SelfHosted.entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 4; @@ -801,6 +821,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.pushdeer.self.ios.Clip; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "PushDeer/PushDeer-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -870,6 +892,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-SH"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "PushDeer/PushDeer-SelfHosted.entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 4; @@ -897,6 +920,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "PushDeer/PushDeer-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -908,6 +932,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-SH"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "PushDeerClip/PushDeerClip-SelfHosted.entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 4; @@ -938,6 +963,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.pushdeer.self.ios.Clip; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "PushDeer/PushDeer-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1067,6 +1093,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = PushDeer/PushDeer.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 4; @@ -1094,6 +1121,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "PushDeer/PushDeer-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1105,6 +1134,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = PushDeer/PushDeer.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 4; @@ -1132,6 +1162,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "PushDeer/PushDeer-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1143,6 +1174,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = PushDeerClip/PushDeerClip.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 4; @@ -1173,6 +1205,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.pushdeer.app.ios.Clip; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "PushDeer/PushDeer-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1184,6 +1218,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = PushDeerClip/PushDeerClip.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 4; @@ -1214,6 +1249,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.pushdeer.app.ios.Clip; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "PushDeer/PushDeer-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1262,8 +1298,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/gonzalezreal/MarkdownUI"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.5.2; + kind = upToNextMinorVersion; + minimumVersion = 1.0.0; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/ios/PushDeer-iOS/PushDeer.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/PushDeer-iOS/PushDeer.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index f9b0d7c..ff23ebc 100644 --- a/ios/PushDeer-iOS/PushDeer.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/ios/PushDeer-iOS/PushDeer.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -2,6 +2,8 @@ + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + PreviewsEnabled diff --git a/ios/PushDeer-iOS/PushDeer.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/PushDeer-iOS/PushDeer.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4879aa4..d638329 100644 --- a/ios/PushDeer-iOS/PushDeer.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/PushDeer-iOS/PushDeer.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/gonzalezreal/AttributedText", "state": { "branch": null, - "revision": "c345033e22d5a1cd0e9fe0ec405cc809a8349586", - "version": "0.3.1" + "revision": "2dc2d7864e0fee4b524a5850d7d7cf9a7eeda0fc", + "version": "1.0.0" } }, { @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/gonzalezreal/MarkdownUI", "state": { "branch": null, - "revision": "29d94710545952dd4c724cc2ca901848eef54ded", - "version": "0.5.2" + "revision": "94e07c111f1ef0a9c997d2fadac984e8bb7d7405", + "version": "1.0.0" } }, { @@ -33,17 +33,8 @@ "repositoryURL": "https://github.com/gonzalezreal/NetworkImage", "state": { "branch": null, - "revision": "04167e81ed89a14b5da9b856ead306b969bb5abd", - "version": "3.1.2" - } - }, - { - "package": "cmark", - "repositoryURL": "https://github.com/SwiftDocOrg/swift-cmark.git", - "state": { - "branch": null, - "revision": "9c8096a23f44794bde297452d87c455fc4f76d42", - "version": "0.29.0+20210102.9c8096a" + "revision": "f8b8ed0be39d2f4aa00a6e8d3b18a62a94eff8d8", + "version": "4.0.0" } }, { @@ -51,8 +42,8 @@ "repositoryURL": "https://github.com/gonzalezreal/SwiftCommonMark", "state": { "branch": null, - "revision": "ed60da54305c244d0f77bc8d08495e04161e96a4", - "version": "0.1.2" + "revision": "ed252beaddecce28ea6363f800c773d6169011b8", + "version": "1.0.0" } }, { diff --git a/ios/PushDeer-iOS/PushDeer/AppDelegate.swift b/ios/PushDeer-iOS/PushDeer/AppDelegate.swift index 6cfd7ed..9ea971a 100644 --- a/ios/PushDeer-iOS/PushDeer/AppDelegate.swift +++ b/ios/PushDeer-iOS/PushDeer/AppDelegate.swift @@ -9,8 +9,9 @@ import UIKit import UserNotifications import IQKeyboardManagerSwift +@MainActor class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { - + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { // 注册通知 @@ -35,6 +36,12 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele IQKeyboardManager.shared.enable = false // 键盘与输入框的距离管理 禁用 IQKeyboardManager.shared.enableAutoToolbar = true // 键盘上方添加的工具栏 启用 +#if !targetEnvironment(macCatalyst) && !APPCLIP && !SELFHOSTED + // 非Mac and 非AppClip and 非自架版 + // 向微信注册 + WXApi.registerApp(Env.wxAppid, universalLink: Env.wxUniversalLink) +#endif + return true } diff --git a/ios/PushDeer-iOS/PushDeer/Assets.xcassets/Colors/weixinBgColor.colorset/Contents.json b/ios/PushDeer-iOS/PushDeer/Assets.xcassets/Colors/weixinBgColor.colorset/Contents.json new file mode 100644 index 0000000..9a004e3 --- /dev/null +++ b/ios/PushDeer-iOS/PushDeer/Assets.xcassets/Colors/weixinBgColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "100", + "green" : "191", + "red" : "31" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.000", + "blue" : "0", + "green" : "0", + "red" : "0" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/PushDeer-iOS/PushDeer/Assets.xcassets/Colors/weixinFgColor.colorset/Contents.json b/ios/PushDeer-iOS/PushDeer/Assets.xcassets/Colors/weixinFgColor.colorset/Contents.json new file mode 100644 index 0000000..c90803e --- /dev/null +++ b/ios/PushDeer-iOS/PushDeer/Assets.xcassets/Colors/weixinFgColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "100", + "green" : "191", + "red" : "31" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/PushDeer-iOS/PushDeer/Assets.xcassets/Images/weixin-login.imageset/Contents.json b/ios/PushDeer-iOS/PushDeer/Assets.xcassets/Images/weixin-login.imageset/Contents.json new file mode 100644 index 0000000..e4e683d --- /dev/null +++ b/ios/PushDeer-iOS/PushDeer/Assets.xcassets/Images/weixin-login.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "微信LOGO_轮廓.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/PushDeer-iOS/PushDeer/Assets.xcassets/Images/weixin-login.imageset/微信LOGO_轮廓.png b/ios/PushDeer-iOS/PushDeer/Assets.xcassets/Images/weixin-login.imageset/微信LOGO_轮廓.png new file mode 100644 index 0000000000000000000000000000000000000000..89ad2ded31a19238aa4007b88d04cdb96a043d2a GIT binary patch literal 3078 zcmXX|cRUpSAHEJpLPkdVlD+vVWE3tOnaR$)JMlHL$CWMP+{tz#Cqi9Djx&!kGc!BR zCVQOBGD3d()$8^Cyr1WJKhNv^`uz9#pp1=l80op`0RRA_?maD&bIdqDPwA-6`$nG_ z@*Lh68<;`P5!3=bFF-pC&;q^C3_Ir+S{(ii|8&|`C~XUjw)M9KYW-9H=YM1i^gKY* z3cc6@z0?Lf574&!mVd*)`QPw)?A-lrolpLfKl1m6zxjS=P&*9N=?MDb0_u4L8vGB` z>vSqJzk7aB8GjQaQ^4^N70t!(mp2zbO=YO_e|+;wlrUt`3sJtt?y4s}S`u+{^@&%E zt#M5f9R1bLWui_E0HB}O)w*l;Wa2{>%48nQ981cHmcu@KazUa@N`U0obXMTuTxf>w zh9^!p>6uhoQFDQkX> zQg&cqE8bRp^^Q58ZGI2 zkM{;MTCY1l*8>8MBjYYaSiJ8hHOXg1)w80Rtu9`mZznTJ?Yi;0)pKC6IeI8ScX@@o zQ`^0n>a~BrGDMm)^X-s3a?%rCxD8oSf095a4{vk`yQNF?S7}@5yDZ_giSsVXrqO1X zh{ZQ# zjM-k)$c$h#$c2EBLg$Zg2T5`%ow)_4r2ESrMH5x>oL+uZft!7^LqVa+hz6&f7hPC+ zBKyo4paNHH>*GI~0=_FE#L5sax#pNB8#}m95-W2#n7wn-E|%`ZhtZZO)c6@!K_fm~ z$Zf$o?MF8nSxfjv^4&S~v|{13%IwX}i0bQQj{a7ikSbwT%kv<4d;Qj~-a&oDr6*oN z2L8+~@_Y^E6I?oMF>_x2B`~az=cG42%Bm~;n5*gjgh{!*%l-qCSL@oblsvpTo=4fr z8Td=%^i^2=ICUQYh+%_n~7+Lw9+;h?yAi~ zc4}>t=6qQU^nKfhR5H;9gL;2p}n zC)9e2J*VN_^N{IFaCyz-wZ3M$>zWMALM!t`#qnOR*l&Uc)*aRjmuxiox{1z* zqg*<$HLfT=Ojx4|!={?3scxB)FEZEj41YLV5qIRexy1Eo@l*?osH9E0f@`5~-*KvQ zzqIj848Wdvff;MpmqSVwd=PxJT{G^qu=6DK+hu%|>(7GDOQGXqgOSFW{hmL&hZ~<> zbwG6aIx-9_F>%*_-0;Y<6>vwreV1HB#@o!QAna%G4I1K-TuPiuM}oR#3^q=Fqs#E} zWstnZquMfKpGL061na4LvA_7Klq(imCX-7E0}IHpqm5iRf7Yw)kOmFu%ETW7s&O&4 zx{HJNe8*>}*bR4Vw0^v%ll}*5Gj%82&=%`Opt2MaF?0{V#;XuOGmHZ&*o57&$Vz+z z+iuIH7xTScqdDMcQGbg=Ru1KNyd2&%DEmmA;{g*nl<`VwXUYg$vRa7Mqd`UB=d5P{ z;WO1cmA}S@v-Mf&U+Pp)+U(s+je`I)Dw1Ob#W4`RF#)})<~$r-@e&wDFqH`(c&N$e z!t%zma{LBn8Vo2NRhzRm$zca(P6ZW9mmGjWeaGFVNmUM_*S9%mTP(%h68nb&jrRE# zxZ8CwPm75nP=%QKy^r=@m_A4sUVXWYpvEp(HQn%U8LVVo9UM?AS_9B*yFzNu;J#jgwUah4AgG2r5^ix>q3Y&XBU_#L-Rola5D$)2 z%R2QMFrLOJVrUrauE=1nbAPp@4;Niq*7ptwb*-&diaB-r=fG6??z1!3hKtZ>&}+}2 zz?HO>VczR$u+$G*(Ko*>=48yP$xxd=-wVqovVKjWX7$Cp!Ic@s1xV>6H-%e0-F z7;_o1?_XsEgsugYoH19+l~>XqBHpH`XBuwh5uWhlR0*i-BdBH#DTHvP>5%mx>eUW{ z3@Pk!5m1X{`%b!U&xX=r0`Bkb$d{Oyj`s(;=2kg*$PALvn{sH{553|KZxBtsL&a=1 z2YwFATB~^?B?azR>r0I6jnV0}9=rNl$@wUCMJ3@q@EeVQfR$~in3j{j(Lxl$~T6L93|8vSi&8P^{pL;K?0 zAG4_!*MxmYnG6HU)I>$LUEfbzY-=9QBCb{2-c)Esca!~&&Ysi|kz8WOO2$R`c40LU z6G88hetcM9b>6D#x3{MyEyhaA>vy(%0($o9Q@~73Dxf@b>1lF%?J0WoDQ)rX~NwhT20;clQbi;=b3BcS^p`C*m!eE=3wfXob!F&~hXrwegyx z#?+iEg$tsn&Ks0A{`gBnzku)bCte__Xf(;`%DePsZOHrw8hs9)w=mH|g3G{k1uh|% zL%jH?m+}Mh;Dp6l%pt&Aiqy+tUh(SlvpESBJdOG~J)Npm=zP3X=dbC9r;CAg&oX3i z>`&IoPB|1=N1pWf;wjm~I=8ABooruLDL~SJt@c@2ZcedN^0ACX#^v@Hv-i50ac%FP zuSS!YZSf4UbnM6mKGW5z3O=(C))TXUp|S4U^kd(_U#*YdDbW-b?=0?KZ(R`joH|x*y+uftvMJ81`bR!) z2`R_i8Pt?eixBqjnU&l1YTZ3-x(~C@zlouQ(-)Y&m%ZO*e*$u4_&429NmKazpA682 L7-^MhK%e~&Y!Bmb literal 0 HcmV?d00001 diff --git a/ios/PushDeer-iOS/PushDeer/Common/SafariView.swift b/ios/PushDeer-iOS/PushDeer/Common/SafariView.swift new file mode 100644 index 0000000..3757ed2 --- /dev/null +++ b/ios/PushDeer-iOS/PushDeer/Common/SafariView.swift @@ -0,0 +1,24 @@ +// +// SafariView.swift +// PushDeer +// +// Created by HEXT on 2022/2/27. +// + +import SwiftUI +import SafariServices + +struct SafariView: UIViewControllerRepresentable { + + let url: URL + + func makeUIViewController(context: Context) -> SFSafariViewController { + let safariVC = SFSafariViewController.init(url: url) + safariVC.dismissButtonStyle = .close + return safariVC + } + + func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) { + // update code + } +} diff --git a/ios/PushDeer-iOS/PushDeer/Env.swift b/ios/PushDeer-iOS/PushDeer/Env.swift index 7008e82..c0bdee5 100644 --- a/ios/PushDeer-iOS/PushDeer/Env.swift +++ b/ios/PushDeer-iOS/PushDeer/Env.swift @@ -21,4 +21,9 @@ struct Env { static let onlineApiEndpoint = "https://api2.pushdeer.com" /// AppStore 的 appId, 自建版: 1608017631; 在线版: 1596771139 static let appStoreId = isSelfHosted ? 1608017631 : 1596771139 + /// 微信开发者ID + static let wxAppid = "wx3ae07931d0555a24" + /// 微信开发者Universal Link + static let wxUniversalLink = "https://vip.pushdeer.com/app/" + } diff --git a/ios/PushDeer-iOS/PushDeer/Info.plist b/ios/PushDeer-iOS/PushDeer/Info.plist index 46e9daf..1bb74ca 100644 --- a/ios/PushDeer-iOS/PushDeer/Info.plist +++ b/ios/PushDeer-iOS/PushDeer/Info.plist @@ -2,8 +2,26 @@ + LSApplicationQueriesSchemes + + weixin + weixinULAPI + CFBundleAllowMixedLocalizations + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + weixin + CFBundleURLSchemes + + wx3ae07931d0555a24 + + + NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ios/PushDeer-iOS/PushDeer/Model/Result.swift b/ios/PushDeer-iOS/PushDeer/Model/Result.swift index 5ba7525..6a2b994 100644 --- a/ios/PushDeer-iOS/PushDeer/Model/Result.swift +++ b/ios/PushDeer-iOS/PushDeer/Model/Result.swift @@ -22,9 +22,9 @@ struct TokenContent: Codable{ struct UserInfoContent: Codable{ let id: Int - let name: String + let name: String? let email: String? - let apple_id: String + let apple_id: String? let wechat_id: String? let level: Int let created_at: String @@ -74,7 +74,7 @@ struct ActionContent: Codable{ let message: String } -struct PushResultContent: Codable{ +struct ResultContent: Codable{ let result: Array } diff --git a/ios/PushDeer-iOS/PushDeer/PushDeer-Bridging-Header.h b/ios/PushDeer-iOS/PushDeer/PushDeer-Bridging-Header.h new file mode 100644 index 0000000..b18a4f5 --- /dev/null +++ b/ios/PushDeer-iOS/PushDeer/PushDeer-Bridging-Header.h @@ -0,0 +1,7 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#if __has_include("WXApi.h") +#import "WXApi.h" +#endif diff --git a/ios/PushDeer-iOS/PushDeer/PushDeer.entitlements b/ios/PushDeer-iOS/PushDeer/PushDeer.entitlements index 09987df..9cc65b4 100644 --- a/ios/PushDeer-iOS/PushDeer/PushDeer.entitlements +++ b/ios/PushDeer-iOS/PushDeer/PushDeer.entitlements @@ -11,6 +11,7 @@ com.apple.developer.associated-domains appclips:vip.pushdeer.com + applinks:vip.pushdeer.com com.apple.security.app-sandbox diff --git a/ios/PushDeer-iOS/PushDeer/PushDeerApp.swift b/ios/PushDeer-iOS/PushDeer/PushDeerApp.swift index bbe7875..dab7bc5 100644 --- a/ios/PushDeer-iOS/PushDeer/PushDeerApp.swift +++ b/ios/PushDeer-iOS/PushDeer/PushDeerApp.swift @@ -16,6 +16,18 @@ struct PushDeerApp: App { var body: some Scene { WindowGroup { ContentView() + .onOpenURL(perform: { url in + print(#function, url) +#if !targetEnvironment(macCatalyst) && !APPCLIP && !SELFHOSTED + WXApi.handleOpen(url, delegate: WXDelegate.shared) +#endif + }) + .onContinueUserActivity(NSUserActivityTypeBrowsingWeb, perform: { userActivity in + print(#function, userActivity.webpageURL as Any) +#if !targetEnvironment(macCatalyst) && !APPCLIP && !SELFHOSTED + WXApi.handleOpenUniversalLink(userActivity, delegate: WXDelegate.shared) +#endif + }) .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in // 后台进入前台后, 清空未读消息角标 UIApplication.shared.applicationIconBadgeNumber = 0 diff --git a/ios/PushDeer-iOS/PushDeer/Service/AppState.swift b/ios/PushDeer-iOS/PushDeer/Service/AppState.swift index 27a3a36..bcfe920 100644 --- a/ios/PushDeer-iOS/PushDeer/Service/AppState.swift +++ b/ios/PushDeer-iOS/PushDeer/Service/AppState.swift @@ -37,6 +37,12 @@ class AppState: ObservableObject { UserDefaults.standard.set(isShowTestPush, forKey: "PushDeer_isShowTestPush") } } + /// 是否使用内置浏览器打开链接 + @Published var isUseBuiltInBrowser: Bool { + didSet { + UserDefaults.standard.set(isUseBuiltInBrowser, forKey: "PushDeer_isUseBuiltInBrowser") + } + } /// API endpoint @Published var api_endpoint : String { @@ -59,10 +65,12 @@ class AppState: ObservableObject { let _token = UserDefaults.standard.string(forKey: "PushDeer_token") let _tabSelectedIndex = UserDefaults.standard.integer(forKey: "PushDeer_tabSelectedIndex") let _isShowTestPush = UserDefaults.standard.object(forKey: "PushDeer_isShowTestPush") + let _isUseBuiltInBrowser = UserDefaults.standard.object(forKey: "PushDeer_isUseBuiltInBrowser") let _api_endpoint = UserDefaults.standard.string(forKey: "PushDeer_api_endpoint") token = _token ?? "" tabSelectedIndex = _tabSelectedIndex isShowTestPush = _isShowTestPush as? Bool ?? true + isUseBuiltInBrowser = _isUseBuiltInBrowser as? Bool ?? true api_endpoint = _api_endpoint ?? "" } @@ -97,6 +105,10 @@ class AppState: ObservableObject { } case let .failure(error): print(error) + if (error as NSError).code == 1001 { + // Apple 登录取消 + throw NSError(domain: NSLocalizedString("登录失败", comment: "AppleId登录失败时提示") + "\n\(error.localizedDescription)", code: 1001, userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("你已取消授权", comment: "")]) + } // Apple 登录失败 throw NSError(domain: NSLocalizedString("登录失败", comment: "AppleId登录失败时提示") + "\n\(error.localizedDescription)", code: -2, userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("登录失败", comment: "AppleId登录失败时提示") + "(-2)\n\(error.localizedDescription)"]) } diff --git a/ios/PushDeer-iOS/PushDeer/Service/HttpRequest.swift b/ios/PushDeer-iOS/PushDeer/Service/HttpRequest.swift index 9563f3b..4c0149d 100644 --- a/ios/PushDeer-iOS/PushDeer/Service/HttpRequest.swift +++ b/ios/PushDeer-iOS/PushDeer/Service/HttpRequest.swift @@ -20,6 +20,12 @@ struct HttpRequest { switch result { case let .success(response): do { + print(response) + if response.statusCode != 200 { + continuation.resume(throwing: NSError(domain: NSLocalizedString("接口报错", comment: "接口报错时提示"), code: response.statusCode, userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("接口报错", comment: "接口报错时提示") + "(\(response.statusCode)"])) + return + } + print((try? response.mapJSON()) ?? "返回值解析失败") let result = try JSONDecoder().decode(ApiResult.self, from: response.data) print(result) if let content = result.content, result.code == 0 { @@ -50,6 +56,19 @@ struct HttpRequest { return try await request(.login(idToken: idToken), resultType: TokenContent.self) } + static func wechatLogin(code: String) async throws -> TokenContent { + return try await request(.wechatLogin(code: code), resultType: TokenContent.self) + } + + /// 合并用户并将旧用户删除 + /// | 参数 | 说明 | + /// | - | - | + /// | type | 字符串,必须为 apple 或者 wechat | + /// | tokenorcode | type 为 apple时此字段为 idToken,否则为 微信code | + static func mergeUser(type: String, tokenorcode: String) async throws -> ResultContent { + return try await request(.mergeUser(token: AppState.shared.token, type: type, tokenorcode: tokenorcode), resultType: ResultContent.self) + } + static func getUserInfo() async throws -> UserInfoContent { return try await request(.getUserInfo(token: AppState.shared.token), resultType: UserInfoContent.self) } @@ -105,8 +124,8 @@ struct HttpRequest { } } - static func push(pushkey: String, text: String, desp: String, type: String) async throws -> PushResultContent { - return try await request(.push(pushkey: pushkey, text: text, desp: desp, type: type), resultType: PushResultContent.self) + static func push(pushkey: String, text: String, desp: String, type: String) async throws -> ResultContent { + return try await request(.push(pushkey: pushkey, text: text, desp: desp, type: type), resultType: ResultContent.self) } static func getMessages() async throws -> MessageContent { diff --git a/ios/PushDeer-iOS/PushDeer/Service/PushDeerApi.swift b/ios/PushDeer-iOS/PushDeer/Service/PushDeerApi.swift index a698182..01541af 100644 --- a/ios/PushDeer-iOS/PushDeer/Service/PushDeerApi.swift +++ b/ios/PushDeer-iOS/PushDeer/Service/PushDeerApi.swift @@ -11,7 +11,17 @@ import Moya enum PushDeerApi { case fake + /// 通过苹果 idToken 登入 case login(idToken: String) + /// 通过微信 oauth code 登入 + case wechatLogin(code: String) + /// 合并用户并将旧用户删除 + /// | 参数 | 说明 | + /// | - | - | + /// | token | 认证token | + /// | type | 字符串,必须为 apple 或者 wechat | + /// | tokenorcode | type 为 apple时此字段为 idToken,否则为 微信code | + case mergeUser(token: String, type: String, tokenorcode: String) case getUserInfo(token: String) case regDevice(token: String, name: String, device_id: String, is_clip: Int) @@ -47,6 +57,10 @@ extension PushDeerApi: TargetType { return "/login/fake" case .login: return "/login/idtoken" + case .wechatLogin: + return "/login/wecode" + case .mergeUser: + return "/user/merge" case .getUserInfo: return "/user/info" @@ -91,6 +105,10 @@ extension PushDeerApi: TargetType { return .requestParameters(parameters: [:], encoding: URLEncoding.queryString) case let .login(idToken): return .requestParameters(parameters: ["idToken": idToken], encoding: URLEncoding.queryString) + case let .wechatLogin(code): + return .requestParameters(parameters: ["code": code], encoding: URLEncoding.queryString) + case let .mergeUser(token, type, tokenorcode): + return .requestParameters(parameters: ["token": token, "type": type, "tokenorcode": tokenorcode], encoding: URLEncoding.queryString) case let .getUserInfo(token): return .requestParameters(parameters: ["token": token], encoding: URLEncoding.queryString) diff --git a/ios/PushDeer-iOS/PushDeer/Service/WXDelegate.swift b/ios/PushDeer-iOS/PushDeer/Service/WXDelegate.swift new file mode 100644 index 0000000..1d5564f --- /dev/null +++ b/ios/PushDeer-iOS/PushDeer/Service/WXDelegate.swift @@ -0,0 +1,57 @@ +// +// WXDelegate.swift +// PushDeer +// +// Created by HEXT on 2022/2/26. +// + +import Foundation + +#if !targetEnvironment(macCatalyst) && !APPCLIP && !SELFHOSTED + +@MainActor +class WXDelegate: NSObject, WXApiDelegate { + + static let shared = WXDelegate() + private override init() { super.init() } + + func onReq(_ req: BaseReq) { + print(#function, req.type, req.openID) + } + func onResp(_ resp: BaseResp) { + print(#function, resp.type, resp.errCode, resp.errStr) + if let resp = resp as? SendAuthResp { // 是登录授权的响应 + print(resp.code as Any, resp.state as Any, resp.lang as Any, resp.country as Any) + switch resp.errCode { + case 0: // 用户同意 + if let code = resp.code, let state = resp.state { + Task { + do { + if state == "login" { + AppState.shared.token = try await HttpRequest.wechatLogin(code: code).token + // 给 AppState 的 token 赋值后, SwiftUI 写的 ContentView 页面会监听到并自动进入主页 + } else if state == "bind" { + _ = try await HttpRequest.mergeUser(type: "wechat", tokenorcode: code) + // 合并成功, 更新数据 + AppState.shared.userInfo = try await HttpRequest.getUserInfo() + } + } catch { + HToast.showError(error.localizedDescription) + } + } + } + break + case -2: // 用户取消 + HToast.showWarning(NSLocalizedString("你已取消授权", comment: "")) + break + case -4: // 用户拒绝授权 + HToast.showError(NSLocalizedString("你已拒绝授权", comment: "")) + break + default: + break + } + } + } +} + +#endif diff --git a/ios/PushDeer-iOS/PushDeer/View/Common/EditableText.swift b/ios/PushDeer-iOS/PushDeer/View/Common/EditableText.swift index 9611adf..fcb0ae7 100644 --- a/ios/PushDeer-iOS/PushDeer/View/Common/EditableText.swift +++ b/ios/PushDeer-iOS/PushDeer/View/Common/EditableText.swift @@ -15,9 +15,16 @@ struct EditableText: View { } var body: some View { - TextField(placeholder, text: $value, onCommit: { - print("修改文本:", value) - self.onCommit(value) + TextField(placeholder, text: $value, onEditingChanged: { focus in + print("focus", focus, placeholder, value) + if !focus { + // 输入框失去焦点的时候也保存 + self.onCommit(value) + } + }, onCommit: { + // enter回车键也会收键盘, 使其失去焦点 + // print("修改文本:", value) + // self.onCommit(value) }) .font(.system(size: 20)) .foregroundColor(Color.accentColor) diff --git a/ios/PushDeer-iOS/PushDeer/View/DeviceItemView.swift b/ios/PushDeer-iOS/PushDeer/View/DeviceItemView.swift index 8906b44..af54a09 100644 --- a/ios/PushDeer-iOS/PushDeer/View/DeviceItemView.swift +++ b/ios/PushDeer-iOS/PushDeer/View/DeviceItemView.swift @@ -9,7 +9,7 @@ import SwiftUI /// 每个设备项的 View struct DeviceItemView: View { - let deviceItem: DeviceItem + @State var deviceItem: DeviceItem @EnvironmentObject private var store: AppState var body: some View { @@ -30,10 +30,14 @@ struct DeviceItemView: View { .padding(EdgeInsets(top: 0, leading: 18, bottom: 0, trailing: 8)) EditableText(placeholder: NSLocalizedString("输入设备名称", comment: ""), value: deviceItem.name) { value in + if deviceItem.name == value { + return + } Task { // 调用接口修改 _ = try await HttpRequest.renameDevice(id: deviceItem.id, name: value) HToast.showSuccess(NSLocalizedString("已修改设备名称", comment: "")) + deviceItem.name = value // 在此 Item 在列表中的下标 let index = store.devices.firstIndex { $0.id == deviceItem.id } if let index = index { @@ -44,7 +48,7 @@ struct DeviceItemView: View { } Text(getInfo(deviceItem: deviceItem)) .font(.system(size: 20)) - Spacer() + Spacer(minLength: 12) } .frame(height: 80) } diff --git a/ios/PushDeer-iOS/PushDeer/View/KeyItemView.swift b/ios/PushDeer-iOS/PushDeer/View/KeyItemView.swift index 34160d8..c838c0c 100644 --- a/ios/PushDeer-iOS/PushDeer/View/KeyItemView.swift +++ b/ios/PushDeer-iOS/PushDeer/View/KeyItemView.swift @@ -9,7 +9,7 @@ import SwiftUI /// 每个 Key 项的 View struct KeyItemView: View { - let keyItem: KeyItem + @State var keyItem: KeyItem @EnvironmentObject private var store: AppState var body: some View { @@ -20,10 +20,14 @@ struct KeyItemView: View { .scaledToFit() .frame(width: 38, height: 38) EditableText(placeholder: NSLocalizedString("输入key名称", comment: ""), value: keyItem.name) { value in + if keyItem.name == value { + return + } Task { // 调用接口修改 _ = try await HttpRequest.renameKey(id: keyItem.id, name: value) HToast.showSuccess(NSLocalizedString("已修改key名称", comment: "")) + keyItem.name = value // 在此 keyItem 在列表中的下标 let index = store.keys.firstIndex { $0.id == keyItem.id } if let index = index { diff --git a/ios/PushDeer-iOS/PushDeer/View/LoginView.swift b/ios/PushDeer-iOS/PushDeer/View/LoginView.swift index 6192f11..b23f2b6 100644 --- a/ios/PushDeer-iOS/PushDeer/View/LoginView.swift +++ b/ios/PushDeer-iOS/PushDeer/View/LoginView.swift @@ -31,6 +31,7 @@ struct LoginView: View { .scaleEffect(1.5) .frame(height: 64) } else { + // 苹果登录按钮 AppleSignInButton( onRequest: { request in request.requestedScopes = [.fullName, .email] @@ -42,6 +43,11 @@ struct LoginView: View { // 获取成功去主页 } catch { showLoading = false + if (error as NSError).code == 1001 { + // 取消登录 + HToast.showWarning(error.localizedDescription) + return + } HToast.showError(error.localizedDescription) } } @@ -49,6 +55,37 @@ struct LoginView: View { .overlay(RoundedRectangle(cornerRadius: 6).stroke(Color.white)) .frame(maxWidth: 375, minHeight: 64, maxHeight: 64) .padding() + +#if !targetEnvironment(macCatalyst) && !APPCLIP && !SELFHOSTED + if WXApi.isWXAppInstalled() { + // 微信登录按钮 + Button { + let req = SendAuthReq() + req.scope = "snsapi_userinfo"; + req.state = "login"; + WXApi.send(req) { b in + print("WXApi.send:", b) + } + // 微信登录请求发出去后面的逻辑在 AppDelegate 的 onResp 回调方法中处理 + } label: { + HStack { + Image("weixin-login") + .resizable() + .renderingMode(.template) + .scaledToFit() + .frame(height:20) + Text("通过微信登录") + } + .font(.system(size: 26, weight: .semibold)) + .foregroundColor(Color("weixinFgColor")) + .frame(maxWidth: 375, minHeight: 64, maxHeight: 64) + } + .background(Color("weixinBgColor")) + .cornerRadius(6) + .overlay(RoundedRectangle(cornerRadius: 6).stroke(Color("weixinFgColor"))) + .padding() + } +#endif } Spacer() } diff --git a/ios/PushDeer-iOS/PushDeer/View/MessageItemView.swift b/ios/PushDeer-iOS/PushDeer/View/MessageItemView.swift index 63bd09b..d5b4759 100644 --- a/ios/PushDeer-iOS/PushDeer/View/MessageItemView.swift +++ b/ios/PushDeer-iOS/PushDeer/View/MessageItemView.swift @@ -44,33 +44,88 @@ struct MessageItemView: View { } } +extension URL: Identifiable { + public var id: Self { self } +} + struct MessageContentView: View { let messageItem: MessageModel + @EnvironmentObject private var store: AppState @State private var image: PlatformImage? = nil + @State private var showUrl: URL? + @State private var showActionSheet = false var body: some View { switch messageItem.type { case "markdown": CardView { VStack(alignment: .leading, spacing: 5) { - Markdown(Document(messageItem.text ?? "")) + Markdown(messageItem.text ?? "") .markdownStyle( - DefaultMarkdownStyle( + MarkdownStyle( font: .system(size: 14), - foregroundColor: UIColor(named: "textColor") ?? UIColor.darkGray + foregroundColor: .init("textColor") ) ) +#if !targetEnvironment(macCatalyst) + .onOpenMarkdownLink { url in + if store.isUseBuiltInBrowser { + showUrl = url + } else { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } + } +#endif if !(messageItem.desp?.isEmpty ?? true) { - Markdown(Document(messageItem.desp!)) + Markdown(messageItem.desp ?? "") .markdownStyle( - DefaultMarkdownStyle( + MarkdownStyle( font: .system(size: 14), - foregroundColor: UIColor(named: "textColor") ?? UIColor.darkGray + foregroundColor: .init("textColor") ) ) +#if !targetEnvironment(macCatalyst) + .onOpenMarkdownLink { url in + if store.isUseBuiltInBrowser { + showUrl = url + } else { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } + } +#endif } } .padding() +#if targetEnvironment(macCatalyst) + .contextMenu { + Button { + UIPasteboard.general.string = (messageItem.text ?? "") + "\n" + (messageItem.desp ?? "") + HToast.showSuccess(NSLocalizedString("已复制", comment: "")) + } label: { + Label("复制",systemImage: "doc.on.doc") + } + } +#endif +#if !targetEnvironment(macCatalyst) + .onLongPressGesture { + UIImpactFeedbackGenerator().impactOccurred() + showActionSheet = true + } + .actionSheet(isPresented: $showActionSheet) { + ActionSheet(title: Text("复制消息内容"), message: nil, buttons: [ + .default(Text("复制"), action: { + UIPasteboard.general.string = (messageItem.text ?? "") + "\n" + (messageItem.desp ?? "") + HToast.showSuccess(NSLocalizedString("已复制", comment: "")) + }), + .cancel() + ]) + } +#endif + } + .fullScreenCover(item: $showUrl) { + + } content: { url in + SafariView(url: url) } case "image": @@ -146,7 +201,7 @@ struct MessageContentView: View { .padding() .contextMenu { Button { - UIPasteboard.general.string = (messageItem.text ?? "") + (messageItem.desp ?? "") + UIPasteboard.general.string = (messageItem.text ?? "") + "\n" + (messageItem.desp ?? "") HToast.showSuccess(NSLocalizedString("已复制", comment: "")) } label: { Label("复制",systemImage: "doc.on.doc") diff --git a/ios/PushDeer-iOS/PushDeer/View/SettingsView.swift b/ios/PushDeer-iOS/PushDeer/View/SettingsView.swift index c8f1c81..e6db527 100644 --- a/ios/PushDeer-iOS/PushDeer/View/SettingsView.swift +++ b/ios/PushDeer-iOS/PushDeer/View/SettingsView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import AuthenticationServices //import StoreKit /// 设置界面 @@ -21,6 +22,14 @@ struct SettingsView: View { } .padding(EdgeInsets(top: 18, leading: 20, bottom: 0, trailing: 20)) +#if !targetEnvironment(macCatalyst) && !APPCLIP && !SELFHOSTED + if WXApi.isWXAppInstalled() { + LoginInfoView() + .zIndex(-1) + .padding(EdgeInsets(top: -30, leading: 20, bottom: 0, trailing: 20)) + } +#endif + if Env.isSelfHosted { SettingsItemView(title: NSLocalizedString("API endpoint", comment: ""), button: NSLocalizedString("重置", comment: "")) { store.api_endpoint = "" @@ -37,6 +46,22 @@ struct SettingsView: View { } .padding(EdgeInsets(top: 18, leading: 20, bottom: 0, trailing: 20)) +#if !targetEnvironment(macCatalyst) + CardView { + HStack{ + Toggle(isOn: $store.isUseBuiltInBrowser) { + Text("使用内置浏览器打开链接") + .font(.system(size: 18)) + .foregroundColor(Color("textColor")) + } + .padding(16) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + } + .frame(height: 74) + } + .padding(EdgeInsets(top: 18, leading: 20, bottom: 0, trailing: 20)) +#endif + Spacer() } } @@ -64,6 +89,122 @@ struct SettingsView: View { } } +struct LoginInfoView: View { + enum AlertType : Identifiable { + var id: Self { self } + case apple + case wechat + } + @EnvironmentObject private var store: AppState + @State private var alertType: AlertType? = nil + static private var coordinator: AppleSignInCoordinator? = nil + var body: some View { + CardView { + HStack(spacing: 16) { + Spacer() + Circle() + .frame(width: 10, height: 10, alignment: .center) + .foregroundColor(store.userInfo?.apple_id?.isEmpty ?? true ? .gray : .green) + Button { + if store.userInfo?.apple_id?.isEmpty ?? true { + alertType = .apple + } else { + HToast.showText(NSLocalizedString("当前已经绑定苹果账号", comment: "")) + } + } label: { + Image(systemName: "applelogo") + .resizable() + .scaledToFit() + .frame( height: 40, alignment: .center) + } + Spacer() + Circle() + .frame(width: 10, height: 10, alignment: .center) + .foregroundColor(store.userInfo?.wechat_id?.isEmpty ?? true ? .gray : .green) + Button { + if store.userInfo?.wechat_id?.isEmpty ?? true { + alertType = .wechat + } else { + HToast.showText(NSLocalizedString("当前已经绑定微信账号", comment: "")) + } + } label: { + Image("weixin-login") + .resizable() + .renderingMode(.template) + .scaledToFit() + .frame(height: 37, alignment: .center) + } + Spacer() + } + .padding(EdgeInsets(top: 32, leading: 0, bottom: 12, trailing: 0)) + } + .alert(item: $alertType) { alertType in + var message = "" + switch alertType { + case .apple: + message = NSLocalizedString("准备绑定苹果账号, 如果你绑定的账号之前已经存在, 则会合并到当前账号. (之前的Key可能会被删除)", comment: "") + case .wechat: + message = NSLocalizedString("准备绑定微信账号, 如果你绑定的账号之前已经存在, 则会合并到当前账号. (之前的Key可能会被删除)", comment: "") + } + + return Alert( + title: Text("温馨提示"), + message: Text(message), + primaryButton: .default( + Text("绑定"), + action: { + switch alertType { + case .apple: + LoginInfoView.coordinator = AppleSignInCoordinator( + onRequest: { request in + request.requestedScopes = [.fullName, .email] + }, + onCompletion: { result in + do { + switch result { + case let .success(authorization): + if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential { + let idToken = String(data:appleIDCredential.identityToken!, encoding: .utf8) + print(idToken as Any) + // 请求接口 + let result = try await HttpRequest.mergeUser(type: "apple", tokenorcode: idToken!) + print(result) + // 合并成功, 更新数据 + store.userInfo = try await HttpRequest.getUserInfo() + } + case let .failure(error): + if (error as NSError).code == 1001 { + HToast.showWarning(NSLocalizedString("你已取消授权", comment: "")) + } else { + HToast.showError(error.localizedDescription) + } + } + } catch { + HToast.showError(error.localizedDescription) + } + } + ) + LoginInfoView.coordinator?.performRequests() + + case .wechat: +#if !targetEnvironment(macCatalyst) && !APPCLIP && !SELFHOSTED + let req = SendAuthReq() + req.scope = "snsapi_userinfo"; + req.state = "bind"; + WXApi.send(req) { b in + print("WXApi.send:", b) + } + // 微信登录请求发出去后面的逻辑在 AppDelegate 的 onResp 回调方法中处理 +#endif + } + } + ), + secondaryButton: .cancel(Text("稍后")) + ) + } + } +} + struct SettingsView_Previews: PreviewProvider { static var previews: some View { SettingsView().environmentObject(AppState.shared) diff --git a/ios/PushDeer-iOS/PushDeer/en.lproj/Localizable.strings b/ios/PushDeer-iOS/PushDeer/en.lproj/Localizable.strings index 4747a38..7453efa 100644 --- a/ios/PushDeer-iOS/PushDeer/en.lproj/Localizable.strings +++ b/ios/PushDeer-iOS/PushDeer/en.lproj/Localizable.strings @@ -1,6 +1,9 @@ /* 在设备列表中标识当前设备 */ "(当前设备)" = "(current device)"; +/* No comment provided by engineer. */ +"绑定" = "Bind"; + /* No comment provided by engineer. */ "保存" = "Save"; @@ -16,6 +19,12 @@ /* No comment provided by engineer. */ "标题" = "Title"; +/* No comment provided by engineer. */ +"当前已经绑定苹果账号" = "Apple account is currently bound"; + +/* No comment provided by engineer. */ +"当前已经绑定微信账号" = "WeChat account is currently bound"; + /* token失效时提示 */ "登录过期" = "Login expired"; @@ -46,6 +55,12 @@ /* No comment provided by engineer. */ "你还未注册当前设备, 注册后才能收到推送, 是否现在注册? (你还可以稍后点击右上角 + 符号添加当前设备)" = "You have not registered the current device, you can receive push notifications after registration, do you want to register now? (You can also click the + symbol in the upper right corner to add the current device later)"; +/* No comment provided by engineer. */ +"你已拒绝授权" = "You have denied authorization"; + +/* No comment provided by engineer. */ +"你已取消授权" = "You have deauthorized"; + /* No comment provided by engineer. */ "您在此应用中的数据都将发送到 endpoint 指向的服务器" = "Your data in this app will be sent to the server pointed to by endpoint"; @@ -79,6 +94,9 @@ /* No comment provided by engineer. */ "设置" = "Settings"; +/* No comment provided by engineer. */ +"使用内置浏览器打开链接" = "Open links using built-in browser"; + /* No comment provided by engineer. */ "输入设备名称" = "Enter device name"; @@ -86,7 +104,7 @@ "输入key名称" = "Enter key name"; /* No comment provided by engineer. */ -"温馨提示" = "Tips"; +"通过微信登录" = "Sign in with WeChat"; /* No comment provided by engineer. */ "图片未加载成功" = "Image did not load successfully"; @@ -106,6 +124,9 @@ /* 退出登录按钮上的文字 */ "退出" = "Logout"; +/* No comment provided by engineer. */ +"温馨提示" = "Tips"; + /* No comment provided by engineer. */ "喜欢PushDeer?" = "Like PushDeer?"; @@ -148,6 +169,12 @@ /* No comment provided by engineer. */ "注册" = "Register"; +/* No comment provided by engineer. */ +"准备绑定苹果账号, 如果你绑定的账号之前已经存在, 则会合并到当前账号. (之前的Key可能会被删除)" = "Prepare to bind the Apple account. If the account you bind already exists, it will be merged into the current account. (The previous Key may be deleted)"; + +/* No comment provided by engineer. */ +"准备绑定微信账号, 如果你绑定的账号之前已经存在, 则会合并到当前账号. (之前的Key可能会被删除)" = "Prepare to bind the WeChat account. If the account you bind already exists, it will be merged into the current account. (The previous Key may be deleted)"; + /* No comment provided by engineer. */ "自定义服务器" = "Custom server"; @@ -157,3 +184,5 @@ /* No comment provided by engineer. */ "Endpoint 格式不正确" = "Endpoint is malformed"; +/* No comment provided by engineer. */ +"复制消息内容" = "Copy message content"; diff --git a/ios/PushDeer-iOS/remove_unsupported_libraries.rb b/ios/PushDeer-iOS/remove_unsupported_libraries.rb new file mode 100644 index 0000000..7b7ced1 --- /dev/null +++ b/ios/PushDeer-iOS/remove_unsupported_libraries.rb @@ -0,0 +1,447 @@ +###### CONSISTENCY BETWEEN MACOS AND IOS ##### +# +# In order to use the same PodFile with MacOS, we need to unlink the libraries that do not support Catalyst, filter +# files in native targets build phases, filter dependencies and make sure the unsupported frameworks along with their +# their bundle resources are not included in the final archive. For that, we use `platform_filter` to specify 'ios' and +# 'OTHER_LDFLAGS[sdk=iphone*]' to link those libraries for iPhone and iPad. Besides, we modify "*frameworks.sh" and +# "*resrouces.sh" to skip installation for architecture x86_64. +# +# *Notice*: 'sdk=iphone*' excludes macOS, even though Catalyst is compiled with iOS SDK. +# +# ADDING A NEW LIBRARY +# +# Pass the name of the new library to the script +# +###### RESOURCES ####### +# +# https://www.bitbuildr.com/tech-blog/mac-catalyst-porting-an-app-using-crashlytics-firebase - Article that inspired this script +# https://github.com/CocoaPods/Xcodeproj - Xcode configuration using ruby. This Framework is already included on cocoapods environment +# https://www.rubydoc.info/gems/xcodeproj/Xcodeproj/Project/Object/AbstractTarget Wiki for Xcodeproj +# + +include Xcodeproj::Project::Object +include Pod + +$verbose = false + +def loggs string + if $verbose + puts string + end + return +end + +# EXTENSIONS + +class String + def filter_lines + lines = [] + each_line do |line| + if yield line + lines = lines + [line] + end + end + return lines + end +end + +class PBXNativeTarget + + ###### STEP 4 ###### + # In "Pods-" targets, modify "*frameworks.sh" to not install unsupported frameworks for platform architectures + def uninstall_frameworks frameworks, platform, configurations + uninstall frameworks, "#{name}-frameworks.sh", platform.architectures, configurations + end + + ###### STEP 5 ###### + # In "Pods-" targets, modify "*resources.sh" to not install unsupported frameworks for platform architectures + def uninstall_resources resources, platform, configurations + uninstall resources, "#{name}-resources.sh", platform.architectures, configurations + end + + def support_files_folder + build_configurations.filter do |config| not config.base_configuration_reference.nil? end.first.base_configuration_reference.real_path.parent + end + + @private + def uninstall keys, file_name, architectures, configurations=nil + configurations = configurations.nil? ? build_configurations.map { |b| b.name } : configurations + keys = keys.to_set.to_a + loggs "\t\t\tUninstalling for configurations: #{configurations}" + if support_files_folder.nil? + loggs "\t\t\tNothing to uninstall" + return + end + + script_path = support_files_folder.join file_name + if !script_path.exist? + loggs "\t\t\tNothing to uninstall" + return + end + + script = File.read(script_path) + snippets = script.scan(/if \[\[ \"\$CONFIGURATION\" [\S\s]*?(?=fi\n)fi/) + condition = architectures.map do |arch| "[ \"$ARCHS\" != \"#{arch}\" ]" end.reduce("") do |total, piece| total.empty? ? piece : total + " || " + piece end + changed = false + + snippets.filter do |snippet| + configurations.map do |string| snippet.include? string end.reduce(false) do |total, condition| total = total || condition end + end.each do |snippet| + new_snippet = snippet.clone + keys.each do |key| + lines_to_replace = snippet.filter_lines do |line| line.include? "#{key}" end.to_set.to_a + unless lines_to_replace.empty? + changed = true + lines_to_replace.each do |line| + new_snippet.gsub! line, "\tif #{condition}; then \n\t#{line}\tfi\n" + end + end + end + script.gsub! snippet, new_snippet + end + + if changed + File.open(script_path, "w") { |file| file << script } + end + loggs "\t\t\t#{changed ? "Succeded" : "Nothing to uninstall"}" + end + + ###### STEP 1 ###### + # In native target's build phases, add platform filter to: + # - Resources + # - Compile Sources + # - Frameworks + # - Headers + def add_platform_filter_to_build_phases platform + loggs "\t\t- Filtering resources" + resources_build_phase.files.to_a.map do |build_file| build_file.platform_filter = platform.name end + + loggs "\t\t- Filtering compile sources" + source_build_phase.files.to_a.map do |build_file| build_file.platform_filter = platform.name end + + loggs "\t\t- Filtering frameworks" + frameworks_build_phase.files.to_a.map do |build_file| build_file.platform_filter = platform.name end + + loggs "\t\t- Filtering headers" + headers_build_phase.files.to_a.map do |build_file| build_file.platform_filter = platform.name end + end + +end + +class PodTarget + + def module_name + string = name.clone.gsub! /-iOS[0-9]+(\.[0-9])+$/, '' + return string.nil? ? name : string + end + + def resources + return file_accessors.flat_map do |accessor| accessor.resources end.map do |path| "#{path.basename}" end + end + + def vendor_products + return file_accessors.flat_map do |accessor| + accessor.vendored_frameworks + accessor.vendored_libraries + end.map do |s| s.basename + end.map do |s| + name = "#{s}" + if name.include? "framework" + PodDependency.newFramework name.sub(".framework", "") + else + PodDependency.newLibrary name.sub("lib", "").sub(".a", "") + end + end + end + + def frameworks + return file_accessors.flat_map do |accessor| + accessor.spec_consumer.frameworks.map do |name| PodDependency.newFramework name end + accessor.spec_consumer.libraries.map do |name| PodDependency.newLibrary name end + end + end + +end + +class PBXTargetDependency + def module_name + string = name.clone.gsub! /-iOS[0-9]+(\.[0-9])+$/, '' + return string.nil? ? name : string + end +end + +class AbstractTarget + + def module_name + string = name.clone.gsub! /-iOS[0-9]+(\.[0-9])+$/, '' + return string.nil? ? name : string + end + + ###### STEP 2 ###### + # In all targets (aggregates + native), filter dependencies + def add_platform_filter_to_dependencies platform + loggs "\t\t- Filtering dependencies" + dependencies.each do |dependency| + dependency.platform_filter = platform.name + end + end + + ###### STEP 3 ###### + # If any unsupported library, then flag as platform-dependant for every build configuration + def flag_libraries libraries, platform + loggs "\tTarget: #{name}" + build_configurations.filter do |config| not config.base_configuration_reference.nil? + end.each do |config| + loggs "\t\tScheme: #{config.name}" + xcconfig_path = config.base_configuration_reference.real_path + xcconfig = File.read(xcconfig_path) + + changed = false + libraries.each do |framework| + if xcconfig.include? framework + xcconfig.gsub!(framework, '') + unless xcconfig.include? "OTHER_LDFLAGS[sdk=#{platform.sdk}]" + changed = true + xcconfig += "OTHER_LDFLAGS[sdk=#{platform.sdk}] = $(inherited) -ObjC " + end + xcconfig += framework + ' ' + end + end + + File.open(xcconfig_path, "w") { |file| file << xcconfig } + loggs "\t\t\t#{changed ? "Succeded" : "Nothing to flag"}" + end + end + + def to_dependency + # We return both as we don't know if build as library or framework + return [PodDependency.newFramework(module_name), PodDependency.newLibrary(module_name)] + end + + # Dependencies contained in Other Linker Flags + def other_linker_flags_dependencies + frameworks = Array.new + libraries = Array.new + + config = build_configurations.filter do |config| not config.base_configuration_reference.nil? end.first + xcconfig_path = config.base_configuration_reference.real_path + xcconfig = File.read(xcconfig_path) + xcconfig.gsub!(/\r\n?/, "\n") + + xcconfig.each_line do |line| + if line.start_with? 'OTHER_LDFLAGS' + frameworks = frameworks + line.split("-framework").map do |s| + s.strip.delete("\n") end.filter do |s| + s.strip.start_with? '"' end + libraries = libraries + line.split("-l").filter do |s| s.strip.start_with? '"' end.map do |s| s.strip.split(' ').first end + end + end + + libraries = libraries.map do |name| PodDependency.newLibrary(name.gsub!("\"", "")) end + frameworks = frameworks.map do |name| PodDependency.newFramework(name.gsub!("\"", "")) end + + return OtherLinkerFlagsDependencies.new libraries, frameworks + end +end + +# HELPER CLASSES + +class PodDependency + attr_reader :name + attr_reader :type + + def link + if library? + return "-l\"#{name}\"" + else + return "-framework \"#{name}\"" + end + end + + def library? + return type == "library" + end + + def framework? + return type == "framework" + end + + def self.newFramework name + return PodDependency.new(name, "framework") + end + + def self.newLibrary name + return PodDependency.new(name, "library") + end + + def ==(other) + name == other.name && type == other.type + end + + def eql?(other) + name == other.name + end + + private + def initialize(name, type) + @name = name + @type = type + end + +end + +class OtherLinkerFlagsDependencies + attr_reader :libraries + attr_reader :frameworks + + def initialize(libraries = [], frameworks = []) + @libraries = libraries + @frameworks = frameworks + end + + def combine dependencies + frameworks = (dependencies.frameworks + @frameworks).to_set.to_a + libraries = (dependencies.libraries + @libraries).to_set.to_a + return OtherLinkerFlagsDependencies.new libraries, frameworks + end + + def dependencies + libraries + frameworks + end + +end + +class OSPlatform + attr_reader :sdk + attr_reader :name + attr_reader :architectures + + def self.ios + OSPlatform.new 'ios', 'iphone*', ['arm64', 'armv7s', 'armv7'] + end + + def self.macos + OSPlatform.new 'macos', 'macosx*', ['x86_64'] + end + + def self.wtachos + OSPlatform.new 'watchos', 'watchos*', ['arm64_32', 'armv7k'] + end + + def self.tvos + OSPlatform.new 'tvos', 'appletvos*', ['arm64'] + end + + private + def initialize(name, sdk, architectures) + @name = name + @sdk = sdk + @architectures = architectures + end + +end + +# SCRIPT + +class Installer + + def configure_support_catalyst pod_names_to_keep, pod_names_to_remove, configurations=nil + + ###### Variable definition ###### + targets = pods_project.targets + + pod_names_to_remove = pod_names_to_remove.map do |name| name.sub('/', '') end + pod_names_to_keep = pod_names_to_keep.map do |name| name.sub('/', '') end + + pod_names_to_keep = recursive_dependencies pod_names_to_keep + pod_names_to_remove = recursive_dependencies(pod_names_to_remove).filter do |name| !pod_names_to_keep.include? name end + + pod_targets_to_keep = pod_targets.filter do |pod| pod_names_to_keep.include? pod.module_name end # PodTarget + pod_targets_to_remove = pod_targets.filter do |pod| pod_names_to_remove.include? pod.module_name end # PodTarget + + loggs "\n#### Unsupported Libraries ####\n#{pod_names_to_remove}\n" + + targets_to_remove = targets.filter do |target| pod_names_to_remove.include?(target.module_name) end # AbstractTarget + pods_targets = targets.filter do |target| target.name.start_with? "Pods-" end # AbstractTarget + cross_platform_targets = targets.filter do |target| !targets_to_remove.include?(target) && !pods_targets.include?(target) end # AbstractTarget + + ###### Determine which dependencies should be removed ###### + dependencies_to_keep = cross_platform_targets.reduce(OtherLinkerFlagsDependencies.new) do |dependencies, target| + dependencies.combine target.other_linker_flags_dependencies + end.dependencies + + # [PodDependency] + dependencies_to_keep = dependencies_to_keep + cross_platform_targets.flat_map do |target| target.to_dependency end + pod_targets_to_keep.flat_map do |pod| pod.vendor_products + pod.frameworks end + + dependencies_to_remove = targets_to_remove.reduce(OtherLinkerFlagsDependencies.new) do |dependencies, target| + dependencies.combine target.other_linker_flags_dependencies + end.dependencies + + # [PodDependency] + dependencies_to_remove = dependencies_to_remove + targets_to_remove.flat_map do |target| target.to_dependency end + pod_targets_to_remove.flat_map do |pod| pod.vendor_products + pod.frameworks end + dependencies_to_remove = dependencies_to_remove.filter do |d| !dependencies_to_keep.include? d end + + ###### CATALYST NOT SUPPORTED LINKS ###### + unsupported_links = dependencies_to_remove.map do |d| d.link end.to_set.to_a + + loggs "#### Unsupported dependencies ####\n" + loggs "#{dependencies_to_remove.map do |d| d.name end.to_set.to_a }\n\n" + + ###### CATALYST NOT SUPPORTED FRAMEWORKS AND RESOURCES + frameworks_to_uninstall = dependencies_to_remove.filter do |d| d.framework? end.map do |d| "#{d.name}.framework" end.to_set.to_a + resources_to_uninstall = pod_targets_to_remove.flat_map do |pod| pod.resources end.to_set.to_a + + loggs "#### Frameworks not to be included in the Archive ####\n" + loggs "#{frameworks_to_uninstall}\n\n" + + loggs "#### Resources not to be included in the Archive ####\n" + loggs "#{resources_to_uninstall}\n\n" + + ###### OTHER LINKER FLAGS -> to iphone* ###### + loggs "#### Flagging unsupported libraries ####" + targets.each do |target| target.flag_libraries unsupported_links, OSPlatform.ios end + + ###### BUILD_PHASES AND DEPENDENCIES -> PLATFORM_FILTER 'ios' ###### + loggs "\n#### Filtering build phases ####" + targets_to_remove.filter do |target| + pods_project.native_targets.include? target + end.each do |target| + loggs "\tTarget: #{target.name}" + target.add_platform_filter_to_build_phases OSPlatform.ios + target.add_platform_filter_to_dependencies OSPlatform.ios + end + + loggs "\n#### Filtering dependencies ####" + targets_to_remove.filter do |target| + !pods_project.native_targets.include? target + end.each do |target| + loggs "\tTarget: #{target.name}" + target.add_platform_filter_to_dependencies OSPlatform.ios + end + + ###### FRAMEWORKS AND RESOURCES SCRIPT -> if [ "$ARCHS" != "x86_64" ]; then ####### + loggs "\n#### Chagings frameworks and resources script ####" + pods_targets.each do |target| + loggs "\tTarget: #{target.name}" + loggs "\t\t-Uninstalling frameworks" + target.uninstall_frameworks frameworks_to_uninstall, OSPlatform.macos, configurations + + loggs "\t\t-Uninstalling resources" + target.uninstall_resources resources_to_uninstall, OSPlatform.macos, configurations + end + end + + @private + def recursive_dependencies to_filter_names + targets = pods_project.targets + targets_to_remove = targets.filter do |target| to_filter_names.include? target.module_name end + dependencies = targets_to_remove.flat_map do |target| target.dependencies end + dependencies_names = dependencies.map do |d| d.module_name end + + if dependencies.empty? + return to_filter_names + dependencies_names + else + return to_filter_names + recursive_dependencies(dependencies_names) + end + + end + +end