mirror of
https://github.com/easychen/pushdeer.git
synced 2024-11-01 08:09:19 +08:00
commit
52038fbd7b
@ -7,6 +7,10 @@ use_frameworks!
|
||||
def commonPods
|
||||
# Pods for common
|
||||
pod 'Moya', '~> 15.0'
|
||||
pod 'SDWebImageSwiftUI', '~> 2.0.2'
|
||||
pod 'KRProgressHUD', '~> 3.4.7'
|
||||
|
||||
# pod 'WoodPeckeriOS', :configurations => ['Debug']
|
||||
end
|
||||
|
||||
target 'PushDeer' do
|
||||
|
@ -1,22 +1,40 @@
|
||||
PODS:
|
||||
- Alamofire (5.5.0)
|
||||
- KRActivityIndicatorView (3.0.7)
|
||||
- KRProgressHUD (3.4.7):
|
||||
- KRActivityIndicatorView (= 3.0.7)
|
||||
- Moya (15.0.0):
|
||||
- Moya/Core (= 15.0.0)
|
||||
- Moya/Core (15.0.0):
|
||||
- Alamofire (~> 5.0)
|
||||
- SDWebImage (5.12.1):
|
||||
- SDWebImage/Core (= 5.12.1)
|
||||
- SDWebImage/Core (5.12.1)
|
||||
- SDWebImageSwiftUI (2.0.2):
|
||||
- SDWebImage (~> 5.10)
|
||||
|
||||
DEPENDENCIES:
|
||||
- KRProgressHUD (~> 3.4.7)
|
||||
- Moya (~> 15.0)
|
||||
- SDWebImageSwiftUI (~> 2.0.2)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- Alamofire
|
||||
- KRActivityIndicatorView
|
||||
- KRProgressHUD
|
||||
- Moya
|
||||
- SDWebImage
|
||||
- SDWebImageSwiftUI
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Alamofire: 1c4fb5369c3fe93d2857c780d8bbe09f06f97e7c
|
||||
KRActivityIndicatorView: ad69e89c4ce40c986cf580595be4829dcad0e35a
|
||||
KRProgressHUD: a248f0bc6c9c2aed40a37b76e03ffecc7f85c887
|
||||
Moya: 138f0573e53411fb3dc17016add0b748dfbd78ee
|
||||
SDWebImage: 4dc3e42d9ec0c1028b960a33ac6b637bb432207b
|
||||
SDWebImageSwiftUI: 8a3923c95108312b03a599ec1498754af55a6819
|
||||
|
||||
PODFILE CHECKSUM: 71abdebce610eefe1a0299d0a0bd00a463b0a79c
|
||||
PODFILE CHECKSUM: e462e86a9cce18b92c573f662ef405e7091cd912
|
||||
|
||||
COCOAPODS: 1.11.2
|
||||
|
@ -25,13 +25,16 @@
|
||||
52450F432784943F003652D8 /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52450F412784943F003652D8 /* HttpRequest.swift */; };
|
||||
52483FC2277ED6D5003A100E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52483FC1277ED6D5003A100E /* AppDelegate.swift */; };
|
||||
52483FC3277ED6D5003A100E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52483FC1277ED6D5003A100E /* AppDelegate.swift */; };
|
||||
5287FF52278ADC8C00249A0E /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 5287FF51278ADC8C00249A0E /* MarkdownUI */; };
|
||||
5287FF54278AEA5B00249A0E /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 5287FF53278AEA5B00249A0E /* MarkdownUI */; };
|
||||
5287FF59278B3AAE00249A0E /* HToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5287FF58278B3AAE00249A0E /* HToast.swift */; };
|
||||
5287FF5A278B3AAE00249A0E /* HToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5287FF58278B3AAE00249A0E /* HToast.swift */; };
|
||||
5292F4F92776BC7900B9A7BB /* PushDeerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5292F4F82776BC7900B9A7BB /* PushDeerApp.swift */; };
|
||||
5292F4FB2776BC7900B9A7BB /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5292F4FA2776BC7900B9A7BB /* ContentView.swift */; };
|
||||
5292F4FD2776BC7A00B9A7BB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5292F4FC2776BC7A00B9A7BB /* Assets.xcassets */; };
|
||||
5292F5002776BC7A00B9A7BB /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5292F4FF2776BC7A00B9A7BB /* Preview Assets.xcassets */; };
|
||||
52B8CF5F277DE660004CB680 /* AppleSignInButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B8CF5E277DE660004CB680 /* AppleSignInButton.swift */; };
|
||||
52B8CF67277E0B44004CB680 /* PushDeerClipApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B8CF66277E0B44004CB680 /* PushDeerClipApp.swift */; };
|
||||
52B8CF69277E0B44004CB680 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B8CF68277E0B44004CB680 /* ContentView.swift */; };
|
||||
52B8CF6B277E0B46004CB680 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 52B8CF6A277E0B46004CB680 /* Assets.xcassets */; };
|
||||
52B8CF6E277E0B46004CB680 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 52B8CF6D277E0B46004CB680 /* Preview Assets.xcassets */; };
|
||||
52B8CF73277E0B46004CB680 /* PushDeerClip.app in Embed App Clips */ = {isa = PBXBuildFile; fileRef = 52B8CF64277E0B44004CB680 /* PushDeerClip.app */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
@ -58,6 +61,7 @@
|
||||
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 */; };
|
||||
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 */
|
||||
|
||||
@ -99,6 +103,7 @@
|
||||
52450F412784943F003652D8 /* HttpRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpRequest.swift; sourceTree = "<group>"; };
|
||||
52450F442784A95D003652D8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
52483FC1277ED6D5003A100E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
5287FF58278B3AAE00249A0E /* HToast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HToast.swift; sourceTree = "<group>"; };
|
||||
5292F4F52776BC7900B9A7BB /* PushDeer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PushDeer.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5292F4F82776BC7900B9A7BB /* PushDeerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushDeerApp.swift; sourceTree = "<group>"; };
|
||||
5292F4FA2776BC7900B9A7BB /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
@ -107,7 +112,6 @@
|
||||
52B8CF5E277DE660004CB680 /* AppleSignInButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleSignInButton.swift; sourceTree = "<group>"; };
|
||||
52B8CF64277E0B44004CB680 /* PushDeerClip.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PushDeerClip.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
52B8CF66277E0B44004CB680 /* PushDeerClipApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushDeerClipApp.swift; sourceTree = "<group>"; };
|
||||
52B8CF68277E0B44004CB680 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
52B8CF6A277E0B46004CB680 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
52B8CF6D277E0B46004CB680 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
52B8CF6F277E0B46004CB680 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@ -134,6 +138,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
64B0C15E70CCC382B480F76E /* Pods_PushDeer.framework in Frameworks */,
|
||||
5287FF52278ADC8C00249A0E /* MarkdownUI in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -142,6 +147,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4812F19BB0BFEFE089BC253E /* Pods_PushDeerClip.framework in Frameworks */,
|
||||
5287FF54278AEA5B00249A0E /* MarkdownUI in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -227,6 +233,7 @@
|
||||
52B8CF5D277DE5FF004CB680 /* Common */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5287FF58278B3AAE00249A0E /* HToast.swift */,
|
||||
52B8CF5E277DE660004CB680 /* AppleSignInButton.swift */,
|
||||
);
|
||||
path = Common;
|
||||
@ -236,7 +243,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
52B8CF66277E0B44004CB680 /* PushDeerClipApp.swift */,
|
||||
52B8CF68277E0B44004CB680 /* ContentView.swift */,
|
||||
52B8CF6A277E0B46004CB680 /* Assets.xcassets */,
|
||||
52B8CF6F277E0B46004CB680 /* Info.plist */,
|
||||
52B8CF70277E0B46004CB680 /* PushDeerClip.entitlements */,
|
||||
@ -311,6 +317,9 @@
|
||||
52B8CF72277E0B46004CB680 /* PBXTargetDependency */,
|
||||
);
|
||||
name = PushDeer;
|
||||
packageProductDependencies = (
|
||||
5287FF51278ADC8C00249A0E /* MarkdownUI */,
|
||||
);
|
||||
productName = PushDeer;
|
||||
productReference = 5292F4F52776BC7900B9A7BB /* PushDeer.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
@ -330,6 +339,9 @@
|
||||
dependencies = (
|
||||
);
|
||||
name = PushDeerClip;
|
||||
packageProductDependencies = (
|
||||
5287FF53278AEA5B00249A0E /* MarkdownUI */,
|
||||
);
|
||||
productName = PushDeerClip;
|
||||
productReference = 52B8CF64277E0B44004CB680 /* PushDeerClip.app */;
|
||||
productType = "com.apple.product-type.application.on-demand-install-capable";
|
||||
@ -361,6 +373,9 @@
|
||||
Base,
|
||||
);
|
||||
mainGroup = 5292F4EC2776BC7900B9A7BB;
|
||||
packageReferences = (
|
||||
5287FF50278ADC8C00249A0E /* XCRemoteSwiftPackageReference "MarkdownUI" */,
|
||||
);
|
||||
productRefGroup = 5292F4F62776BC7900B9A7BB /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@ -490,6 +505,7 @@
|
||||
52B8CF5F277DE660004CB680 /* AppleSignInButton.swift in Sources */,
|
||||
52163EB92777417900594190 /* KeyListView.swift in Sources */,
|
||||
5292F4FB2776BC7900B9A7BB /* ContentView.swift in Sources */,
|
||||
5287FF59278B3AAE00249A0E /* HToast.swift in Sources */,
|
||||
52450F3B278491F8003652D8 /* AppState.swift in Sources */,
|
||||
5292F4F92776BC7900B9A7BB /* PushDeerApp.swift in Sources */,
|
||||
52483FC2277ED6D5003A100E /* AppDelegate.swift in Sources */,
|
||||
@ -509,7 +525,6 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
52B8CF82277E0C06004CB680 /* AppleSignInButton.swift in Sources */,
|
||||
52B8CF69277E0B44004CB680 /* ContentView.swift in Sources */,
|
||||
52B8CF78277E0BF1004CB680 /* MainView.swift in Sources */,
|
||||
52B8CF79277E0BFB004CB680 /* DeviceListView.swift in Sources */,
|
||||
52B8CF84277E0C12004CB680 /* CardView.swift in Sources */,
|
||||
@ -520,11 +535,13 @@
|
||||
52B8CF81277E0BFB004CB680 /* LoginView.swift in Sources */,
|
||||
52B8CF7F277E0BFB004CB680 /* SettingsItemView.swift in Sources */,
|
||||
52450F3C278491F8003652D8 /* AppState.swift in Sources */,
|
||||
5287FF5A278B3AAE00249A0E /* HToast.swift in Sources */,
|
||||
52B8CF80277E0BFB004CB680 /* MessageItemView.swift in Sources */,
|
||||
52483FC3277ED6D5003A100E /* AppDelegate.swift in Sources */,
|
||||
52450F3927848243003652D8 /* PushDeerApi.swift in Sources */,
|
||||
52B8CF86277E0C12004CB680 /* BaseNavigationView.swift in Sources */,
|
||||
52B8CF67277E0B44004CB680 /* PushDeerClipApp.swift in Sources */,
|
||||
52FBA09427874879003308C2 /* ContentView.swift in Sources */,
|
||||
52B8CF85277E0C12004CB680 /* Line.swift in Sources */,
|
||||
52450F402784923D003652D8 /* Result.swift in Sources */,
|
||||
52450F432784943F003652D8 /* HttpRequest.swift in Sources */,
|
||||
@ -739,6 +756,10 @@
|
||||
DEVELOPMENT_ASSET_PATHS = "\"PushDeerClip/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = Y47WTLML2S;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"APPCLIP=1",
|
||||
);
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = PushDeerClip/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = PushDeer;
|
||||
@ -753,6 +774,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D APPCLIP";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.wskfz.pushdeer.ios.Clip;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
@ -773,6 +795,10 @@
|
||||
DEVELOPMENT_ASSET_PATHS = "\"PushDeerClip/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = Y47WTLML2S;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"APPCLIP=1",
|
||||
);
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = PushDeerClip/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = PushDeer;
|
||||
@ -787,6 +813,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D APPCLIP";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.wskfz.pushdeer.ios.Clip;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
@ -826,6 +853,30 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
5287FF50278ADC8C00249A0E /* XCRemoteSwiftPackageReference "MarkdownUI" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/gonzalezreal/MarkdownUI";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.5.2;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
5287FF51278ADC8C00249A0E /* MarkdownUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5287FF50278ADC8C00249A0E /* XCRemoteSwiftPackageReference "MarkdownUI" */;
|
||||
productName = MarkdownUI;
|
||||
};
|
||||
5287FF53278AEA5B00249A0E /* MarkdownUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5287FF50278ADC8C00249A0E /* XCRemoteSwiftPackageReference "MarkdownUI" */;
|
||||
productName = MarkdownUI;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 5292F4ED2776BC7900B9A7BB /* Project object */;
|
||||
}
|
||||
|
@ -0,0 +1,70 @@
|
||||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "AttributedText",
|
||||
"repositoryURL": "https://github.com/gonzalezreal/AttributedText",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "c345033e22d5a1cd0e9fe0ec405cc809a8349586",
|
||||
"version": "0.3.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "combine-schedulers",
|
||||
"repositoryURL": "https://github.com/pointfreeco/combine-schedulers",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "4cf088c29a20f52be0f2ca54992b492c54e0076b",
|
||||
"version": "0.5.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "MarkdownUI",
|
||||
"repositoryURL": "https://github.com/gonzalezreal/MarkdownUI",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "29d94710545952dd4c724cc2ca901848eef54ded",
|
||||
"version": "0.5.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "NetworkImage",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftCommonMark",
|
||||
"repositoryURL": "https://github.com/gonzalezreal/SwiftCommonMark",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "ed60da54305c244d0f77bc8d08495e04161e96a4",
|
||||
"version": "0.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "xctest-dynamic-overlay",
|
||||
"repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "50a70a9d3583fe228ce672e8923010c8df2deddd",
|
||||
"version": "0.2.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
@ -10,8 +10,6 @@ import UserNotifications
|
||||
|
||||
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
|
||||
|
||||
static var deviceToken: String = ""
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
|
||||
let center = UNUserNotificationCenter.current()
|
||||
@ -23,9 +21,7 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
||||
|
||||
Task {
|
||||
// APP启动后要先调一个无用接口, 用于触发国行手机的网络授权弹框, 未授权前调的接口会直接失败. (提前触发网络授权弹窗)
|
||||
let result = try await HttpRequest.fake()
|
||||
AppState.shared.token = result.token
|
||||
HttpRequest.getDevices()
|
||||
_ = try await HttpRequest.fake()
|
||||
}
|
||||
|
||||
return true
|
||||
@ -34,7 +30,7 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
||||
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||
let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)})
|
||||
print("deviceToken: ", deviceTokenString)
|
||||
AppDelegate.deviceToken = deviceTokenString;
|
||||
AppState.shared.deviceToken = deviceTokenString
|
||||
}
|
||||
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
|
||||
|
@ -13,12 +13,12 @@ import AuthenticationServices
|
||||
/// 封装一个 Apple 登录按钮, 模仿 SwiftUI 中 SignInWithAppleButton 的行为
|
||||
///
|
||||
/// 那么为什么不直接使用系统的呢? 问的好, SignInWithAppleButton 在 macOS 上点击会报错, 在 iOS 上倒是正常.
|
||||
struct AppleSignInButton: UIViewRepresentable {
|
||||
@MainActor struct AppleSignInButton: UIViewRepresentable {
|
||||
|
||||
var type = ASAuthorizationAppleIDButton.ButtonType.signIn
|
||||
var style = ASAuthorizationAppleIDButton.Style.black
|
||||
let onRequest: (ASAuthorizationAppleIDRequest) -> Void
|
||||
let onCompletion: (Result<ASAuthorization, Error>) -> Void
|
||||
let onCompletion: (Result<ASAuthorization, Error>) async -> Void
|
||||
|
||||
func makeUIView(context: Context) -> ASAuthorizationAppleIDButton {
|
||||
let signInButton = ASAuthorizationAppleIDButton(type: type, style: style)
|
||||
@ -40,11 +40,11 @@ struct AppleSignInButton: UIViewRepresentable {
|
||||
class AppleSignInCoordinator: NSObject, ASAuthorizationControllerDelegate {
|
||||
|
||||
let onRequest: (ASAuthorizationAppleIDRequest) -> Void
|
||||
let onCompletion: (Result<ASAuthorization, Error>) -> Void
|
||||
let onCompletion: (Result<ASAuthorization, Error>) async -> Void
|
||||
|
||||
init(
|
||||
onRequest: @escaping (ASAuthorizationAppleIDRequest) -> Void,
|
||||
onCompletion: @escaping (Result<ASAuthorization, Error>) -> Void
|
||||
onCompletion: @escaping (Result<ASAuthorization, Error>) async -> Void
|
||||
) {
|
||||
self.onRequest = onRequest
|
||||
self.onCompletion = onCompletion
|
||||
@ -61,11 +61,15 @@ class AppleSignInCoordinator: NSObject, ASAuthorizationControllerDelegate {
|
||||
|
||||
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
|
||||
print(authorization.debugDescription)
|
||||
onCompletion(.success(authorization))
|
||||
Task {
|
||||
await onCompletion(.success(authorization))
|
||||
}
|
||||
}
|
||||
|
||||
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
|
||||
print(error.localizedDescription)
|
||||
onCompletion(.failure(error))
|
||||
Task {
|
||||
await onCompletion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
29
ios/PushDeer-iOS/PushDeer/Common/HToast.swift
Normal file
29
ios/PushDeer-iOS/PushDeer/Common/HToast.swift
Normal file
@ -0,0 +1,29 @@
|
||||
//
|
||||
// HToast.swift
|
||||
// LotusDesktop
|
||||
//
|
||||
// Created by HEXT on 2020/9/26.
|
||||
// Copyright © 2020 HEXT. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import KRProgressHUD
|
||||
|
||||
struct HToast {
|
||||
static func showText(_ msg: String) {
|
||||
KRProgressHUD.showMessage(msg)
|
||||
}
|
||||
static func showSuccess(_ msg: String?) {
|
||||
KRProgressHUD.showSuccess(withMessage: msg)
|
||||
}
|
||||
static func showError(_ msg: String?) {
|
||||
KRProgressHUD.showError(withMessage: msg)
|
||||
}
|
||||
|
||||
static func showLoading(_ msg: String?) {
|
||||
KRProgressHUD.show(withMessage: msg)
|
||||
}
|
||||
static func dismiss() {
|
||||
KRProgressHUD.dismiss()
|
||||
}
|
||||
}
|
@ -8,11 +8,17 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
|
||||
@EnvironmentObject private var store: AppState
|
||||
|
||||
var body: some View {
|
||||
// LoginView()
|
||||
if store.token.isEmpty {
|
||||
LoginView()
|
||||
} else {
|
||||
MainView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
|
@ -2,14 +2,24 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>APP为您提供保存图片到相册的功能</string>
|
||||
<key>CFBundleAllowMixedLocalizations</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_adhp._tcp</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>APP需要访问本地网络以供调试(仅在开发时)</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -20,6 +20,17 @@ struct TokenContent: Codable{
|
||||
let token: String
|
||||
}
|
||||
|
||||
struct UserInfoContent: Codable{
|
||||
let id: Int
|
||||
let name: String
|
||||
let email: String
|
||||
let apple_id: String
|
||||
let wechat_id: String?
|
||||
let level: Int
|
||||
let created_at: String
|
||||
let updated_at: String
|
||||
}
|
||||
|
||||
struct DeviceItem: Codable, Identifiable{
|
||||
let id: Int
|
||||
let uid: String
|
||||
@ -39,7 +50,10 @@ struct KeyContent: Codable{
|
||||
|
||||
struct KeyItem: Codable, Identifiable{
|
||||
let id: Int
|
||||
let name: String
|
||||
let uid: String
|
||||
let key: String
|
||||
let created_at: String
|
||||
}
|
||||
|
||||
struct MessageContent: Codable{
|
||||
@ -52,8 +66,37 @@ struct MessageItem: Codable, Identifiable{
|
||||
let text: String
|
||||
let desp: String
|
||||
let type: String
|
||||
let created_at: String
|
||||
}
|
||||
|
||||
struct ActionContent: Codable{
|
||||
let message: String
|
||||
}
|
||||
|
||||
struct PushResultContent: Codable{
|
||||
let result: Array<String>
|
||||
}
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
|
||||
extension KeyItem {
|
||||
var createdDateStr: String {
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSZ"
|
||||
let createdDate = dateFormatter.date(from: self.created_at)
|
||||
dateFormatter.dateFormat = "yyyy/MM/dd"
|
||||
return dateFormatter.string(from: createdDate ?? Date())
|
||||
}
|
||||
}
|
||||
|
||||
extension MessageItem {
|
||||
var createdDateStr: String {
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSZ"
|
||||
let createdDate = dateFormatter.date(from: self.created_at) ?? Date()
|
||||
if Calendar.current.isDateInToday(createdDate) {
|
||||
dateFormatter.dateFormat = "HH:mm:ss"
|
||||
} else {
|
||||
dateFormatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
|
||||
}
|
||||
return dateFormatter.string(from: createdDate)
|
||||
}
|
||||
}
|
||||
|
@ -6,14 +6,86 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AuthenticationServices
|
||||
|
||||
class AppState: ObservableObject {
|
||||
@Published var token : String? = nil
|
||||
/// 账号 token
|
||||
@Published var token : String {
|
||||
didSet {
|
||||
UserDefaults.standard.set(token, forKey: "PushDeer_token")
|
||||
}
|
||||
}
|
||||
/// 设备列表
|
||||
@Published var devices: [DeviceItem] = []
|
||||
/// key 列表
|
||||
@Published var keys: [KeyItem] = []
|
||||
/// 消息列表
|
||||
@Published var messages: [MessageItem] = []
|
||||
@Published var tab_selected: Int = 1
|
||||
@Published var device_token: String = ""
|
||||
/// 选中的 tab 下标
|
||||
@Published var tabSelectedIndex: Int {
|
||||
didSet {
|
||||
UserDefaults.standard.set(tabSelectedIndex, forKey: "PushDeer_tabSelectedIndex")
|
||||
}
|
||||
}
|
||||
/// 设备推送 token
|
||||
@Published var deviceToken: String = ""
|
||||
/// 用户信息
|
||||
@Published var userInfo: UserInfoContent?
|
||||
/// 是否显示测试发推送的 UI
|
||||
@Published var isShowTestPush: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(isShowTestPush, forKey: "PushDeer_isShowTestPush")
|
||||
}
|
||||
}
|
||||
|
||||
var isAppClip: Bool {
|
||||
#if APPCLIP
|
||||
return true
|
||||
#else
|
||||
return false
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static let shared = AppState()
|
||||
private init() {}
|
||||
private init() {
|
||||
let _token = UserDefaults.standard.string(forKey: "PushDeer_token")
|
||||
let _tabSelectedIndex = UserDefaults.standard.integer(forKey: "PushDeer_tabSelectedIndex")
|
||||
let _isShowTestPush = UserDefaults.standard.object(forKey: "PushDeer_isShowTestPush")
|
||||
token = _token ?? ""
|
||||
tabSelectedIndex = _tabSelectedIndex
|
||||
isShowTestPush = _isShowTestPush as? Bool ?? true
|
||||
}
|
||||
|
||||
func appleIdLogin(_ result: Result<ASAuthorization, Error>) async throws -> TokenContent {
|
||||
switch result {
|
||||
case let .success(authorization):
|
||||
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
|
||||
// 用户唯一ID,在一个开发者账号下的APP获取到的是一样的
|
||||
print(appleIDCredential.user) // 000791.7a323f1326dd4674bc16d32fd6339875.1424
|
||||
// 注意:当第一次认证成功之后,将不会再返回email,fullName等信息,可以在设置->Apple ID->密码与安全性->使用您AppleID的App 中删除对应的APP。
|
||||
print(appleIDCredential.email as Any) // easychen@qq.com
|
||||
print(appleIDCredential.fullName as Any) // givenName: lijie familyName: chen
|
||||
// 「JWT」格式的token,用于验证信息合法性。其值用.分割成3段, 中间一段base64后会看到包含了 用户唯一标识 和 邮箱 等字段
|
||||
let idToken = String(data:appleIDCredential.identityToken!, encoding: .utf8)
|
||||
print(idToken as Any)
|
||||
|
||||
do {
|
||||
// 请求接口
|
||||
let result = try await HttpRequest.login(idToken: idToken!)
|
||||
print(result)
|
||||
// 登录成功
|
||||
return result
|
||||
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
case let .failure(error):
|
||||
print(error)
|
||||
}
|
||||
// 登录失败
|
||||
throw NSError(domain: "登录失败", code: -1, userInfo: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
import Foundation
|
||||
import Moya
|
||||
|
||||
@MainActor
|
||||
struct HttpRequest {
|
||||
|
||||
static let provider = MoyaProvider<PushDeerApi>(callbackQueue: DispatchQueue.main)
|
||||
@ -23,6 +24,9 @@ struct HttpRequest {
|
||||
print(result)
|
||||
if let content = result.content, result.code == 0 {
|
||||
continuation.resume(returning: content)
|
||||
} else if result.code == 80403 {
|
||||
AppState.shared.token = ""
|
||||
continuation.resume(throwing: NSError(domain: result.error ?? "接口报错", code: result.code, userInfo: nil))
|
||||
} else {
|
||||
continuation.resume(throwing: NSError(domain: result.error ?? "接口报错", code: result.code, userInfo: nil))
|
||||
}
|
||||
@ -46,10 +50,68 @@ struct HttpRequest {
|
||||
return try await request(.login(idToken: idToken), resultType: TokenContent.self)
|
||||
}
|
||||
|
||||
@MainActor static func getDevices() {
|
||||
static func getUserInfo() async throws -> UserInfoContent {
|
||||
return try await request(.getUserInfo(token: AppState.shared.token), resultType: UserInfoContent.self)
|
||||
}
|
||||
|
||||
static func regDevice() async throws -> DeviceContent {
|
||||
return try await request(.regDevice(
|
||||
token: AppState.shared.token,
|
||||
name: UIDevice.current.name,
|
||||
device_id: AppState.shared.deviceToken,
|
||||
is_clip: AppState.shared.isAppClip ? 1 : 0
|
||||
), resultType: DeviceContent.self)
|
||||
}
|
||||
|
||||
static func rmDevice(id: Int) async throws -> ActionContent {
|
||||
return try await request(.rmDevice(token: AppState.shared.token, id: id), resultType: ActionContent.self)
|
||||
}
|
||||
|
||||
static func getDevices() async throws -> DeviceContent {
|
||||
return try await request(.getDevices(token: AppState.shared.token), resultType: DeviceContent.self)
|
||||
}
|
||||
static func loadDevices() {
|
||||
_Concurrency.Task {
|
||||
let result = try await request(.getDevices(token: AppState.shared.token ?? ""), resultType: DeviceContent.self)
|
||||
let result = try await getDevices()
|
||||
AppState.shared.devices = result.devices
|
||||
}
|
||||
}
|
||||
|
||||
static func genKey() async throws -> KeyContent {
|
||||
return try await request(.genKey(token: AppState.shared.token), resultType: KeyContent.self)
|
||||
}
|
||||
|
||||
static func regenKey(id: Int) async throws -> ActionContent {
|
||||
return try await request(.regenKey(token: AppState.shared.token, id: id), resultType: ActionContent.self)
|
||||
}
|
||||
|
||||
static func renameKey(id: Int, name: String) async throws -> ActionContent {
|
||||
return try await request(.renameKey(token: AppState.shared.token, id: id, name: name), resultType: ActionContent.self)
|
||||
}
|
||||
|
||||
static func rmKey(id: Int) async throws -> ActionContent {
|
||||
return try await request(.rmKey(token: AppState.shared.token, id: id), resultType: ActionContent.self)
|
||||
}
|
||||
|
||||
static func getKeys() async throws -> KeyContent {
|
||||
return try await request(.getKeys(token: AppState.shared.token), resultType: KeyContent.self)
|
||||
}
|
||||
static func loadKeys() {
|
||||
_Concurrency.Task {
|
||||
let result = try await getKeys()
|
||||
AppState.shared.keys = result.keys
|
||||
}
|
||||
}
|
||||
|
||||
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 getMessages() async throws -> MessageContent {
|
||||
return try await request(.getMessages(token: AppState.shared.token, limit: 100), resultType: MessageContent.self)
|
||||
}
|
||||
|
||||
static func rmMessage(id: Int) async throws -> ActionContent {
|
||||
return try await request(.rmMessage(token: AppState.shared.token, id: id), resultType: ActionContent.self)
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ enum PushDeerApi {
|
||||
|
||||
case fake
|
||||
case login(idToken: String)
|
||||
case getUserInfo(token: String)
|
||||
|
||||
case regDevice(token: String, name: String, device_id: String, is_clip: Int)
|
||||
case getDevices(token: String)
|
||||
@ -39,6 +40,8 @@ extension PushDeerApi: TargetType {
|
||||
return "/login/fake"
|
||||
case .login:
|
||||
return "/login/idtoken"
|
||||
case .getUserInfo:
|
||||
return "/user/info"
|
||||
|
||||
case .regDevice:
|
||||
return "/device/reg"
|
||||
@ -79,6 +82,8 @@ extension PushDeerApi: TargetType {
|
||||
return .requestParameters(parameters: [:], encoding: URLEncoding.queryString)
|
||||
case let .login(idToken):
|
||||
return .requestParameters(parameters: ["idToken": idToken], encoding: URLEncoding.queryString)
|
||||
case let .getUserInfo(token):
|
||||
return .requestParameters(parameters: ["token": token], encoding: URLEncoding.queryString)
|
||||
|
||||
case let .regDevice(token, name, device_id, is_clip):
|
||||
return .requestParameters(parameters: ["token": token,"name": name, "device_id": device_id,"is_clip": is_clip], encoding: URLEncoding.queryString)
|
||||
|
@ -13,7 +13,7 @@ struct DeviceItemView: View {
|
||||
var body: some View {
|
||||
CardView {
|
||||
HStack{
|
||||
Image(systemName: "ipad.and.iphone")
|
||||
Image(systemName: getSystemName(deviceName: name))
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 40, height: 40, alignment: .center)
|
||||
@ -25,6 +25,37 @@ struct DeviceItemView: View {
|
||||
.frame(height: 80)
|
||||
}
|
||||
}
|
||||
|
||||
func getSystemName(deviceName: String) -> String {
|
||||
var deviceName = deviceName.lowercased()
|
||||
deviceName = deviceName.replacingOccurrences(of: " ", with: "")
|
||||
|
||||
// if deviceName.contains("clip") {
|
||||
// return "appclip"
|
||||
// }
|
||||
if deviceName.contains("iphone") {
|
||||
return "iphone"
|
||||
}
|
||||
if deviceName.contains("ipad") {
|
||||
return "ipad.landscape"
|
||||
}
|
||||
if deviceName.contains("macbook") {
|
||||
return "laptopcomputer"
|
||||
}
|
||||
if deviceName.contains("imac") {
|
||||
return "desktopcomputer"
|
||||
}
|
||||
if deviceName.contains("macpro") {
|
||||
return "macpro.gen3"
|
||||
}
|
||||
if deviceName.contains("macmini") {
|
||||
return "macmini"
|
||||
}
|
||||
if deviceName.contains("mac") {
|
||||
return "macwindow"
|
||||
}
|
||||
return "ipad.and.iphone"
|
||||
}
|
||||
}
|
||||
|
||||
struct DeviceItemView_Previews: PreviewProvider {
|
||||
|
@ -14,13 +14,21 @@ struct DeviceListView: View {
|
||||
BaseNavigationView(title: "设备") {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .center) {
|
||||
ForEach(store.devices, id: \.id) { deviceItem in
|
||||
ForEach(store.devices.reversed()) { deviceItem in
|
||||
DeletableView(contentView: {
|
||||
DeviceItemView(name: deviceItem.name)
|
||||
DeviceItemView(name: getName(deviceItem: deviceItem))
|
||||
}, deleteAction: {
|
||||
store.devices.removeAll { _deviceItem in
|
||||
_deviceItem.id == deviceItem.id
|
||||
}
|
||||
HToast.showSuccess("已删除")
|
||||
Task {
|
||||
do {
|
||||
_ = try await HttpRequest.rmDevice(id: deviceItem.id)
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
.padding(EdgeInsets(top: 18, leading: 26, bottom: 0, trailing: 24))
|
||||
}
|
||||
@ -28,8 +36,12 @@ struct DeviceListView: View {
|
||||
}
|
||||
}
|
||||
.navigationBarItems(trailing: Button(action: {
|
||||
Task {
|
||||
let devices = try await HttpRequest.regDevice().devices
|
||||
withAnimation(.easeOut) {
|
||||
// store.devices.insert(DeviceItem(), at: 0)
|
||||
store.devices = devices
|
||||
}
|
||||
HToast.showSuccess("已添加当前设备")
|
||||
}
|
||||
}, label: {
|
||||
Image(systemName: "plus")
|
||||
@ -37,13 +49,24 @@ struct DeviceListView: View {
|
||||
}))
|
||||
}
|
||||
.onAppear {
|
||||
HttpRequest.getDevices()
|
||||
HttpRequest.loadDevices()
|
||||
}
|
||||
}
|
||||
|
||||
func getName(deviceItem: DeviceItem) -> String {
|
||||
var name = deviceItem.name
|
||||
if deviceItem.is_clip == 1 {
|
||||
name += " [Clip]"
|
||||
}
|
||||
if deviceItem.device_id == store.deviceToken {
|
||||
name += " (当前设备)"
|
||||
}
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
struct DeviceView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
DeviceListView()
|
||||
DeviceListView().environmentObject(AppState.shared)
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,57 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct KeyItemTextField: View {
|
||||
let keyItem: KeyItem
|
||||
@State private var value = ""
|
||||
@EnvironmentObject private var store: AppState
|
||||
|
||||
init(keyItem: KeyItem) {
|
||||
self.keyItem = keyItem
|
||||
self._value = State(initialValue: keyItem.name)
|
||||
}
|
||||
|
||||
func textField() -> some View {
|
||||
TextField("输入key名称", text: $value, onCommit: {
|
||||
Task {
|
||||
do {
|
||||
// 调用接口修改
|
||||
_ = try await HttpRequest.renameKey(id: keyItem.id, name: value)
|
||||
HToast.showSuccess("已修改key名称")
|
||||
// 在此 keyItem 在列表中的下标
|
||||
let index = store.keys.firstIndex { _keyItem in
|
||||
_keyItem.id == keyItem.id
|
||||
}
|
||||
if let index = index {
|
||||
let _keyItem = store.keys[index]
|
||||
// 更新列表中相应的 keyItem
|
||||
store.keys[index] = KeyItem(
|
||||
id: _keyItem.id,
|
||||
name: value,
|
||||
uid: _keyItem.uid,
|
||||
key: _keyItem.key,
|
||||
created_at: _keyItem.created_at
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
.font(.system(size: 20))
|
||||
.foregroundColor(Color.accentColor)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 15.0, *) {
|
||||
textField()
|
||||
.submitLabel(.done)
|
||||
} else {
|
||||
textField()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 每个 Key 项的 View
|
||||
struct KeyItemView: View {
|
||||
let keyItem: KeyItem
|
||||
@ -18,14 +69,12 @@ struct KeyItemView: View {
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 38, height: 38)
|
||||
Text("Key \(keyItem.id)")
|
||||
.font(.system(size: 20))
|
||||
.foregroundColor(Color.accentColor)
|
||||
KeyItemTextField(keyItem: keyItem)
|
||||
Spacer()
|
||||
Image(systemName: "calendar")
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(Color.gray)
|
||||
Text("2021/12/01")
|
||||
Text(keyItem.createdDateStr)
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(Color.gray)
|
||||
}
|
||||
@ -42,6 +91,15 @@ struct KeyItemView: View {
|
||||
HStack {
|
||||
Button("重置") {
|
||||
print("点击重置")
|
||||
Task {
|
||||
do {
|
||||
_ = try await HttpRequest.regenKey(id: keyItem.id)
|
||||
HttpRequest.loadKeys()
|
||||
HToast.showSuccess("已重置")
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.font(.system(size: 20))
|
||||
.frame(width: 90, height: 42)
|
||||
@ -53,6 +111,7 @@ struct KeyItemView: View {
|
||||
Button("复制") {
|
||||
print("点击复制")
|
||||
UIPasteboard.general.string = keyItem.key
|
||||
HToast.showSuccess("已复制")
|
||||
}
|
||||
.font(.system(size: 20))
|
||||
.frame(width: 90, height: 42)
|
||||
@ -67,6 +126,6 @@ struct KeyItemView: View {
|
||||
|
||||
struct KeyItemView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
KeyItemView(keyItem: KeyItem(id: 1, key: "Key"))
|
||||
KeyItemView(keyItem: KeyItem(id: 1, name: "name", uid: "1", key: "Key", created_at: "1111"))
|
||||
}
|
||||
}
|
||||
|
@ -9,26 +9,29 @@ import SwiftUI
|
||||
|
||||
/// Key 界面
|
||||
struct KeyListView: View {
|
||||
@State private var keyItems = [
|
||||
KeyItem(id: 1, key: UUID().uuidString),
|
||||
KeyItem(id: 2, key: UUID().uuidString),
|
||||
KeyItem(id: 3, key: UUID().uuidString),
|
||||
KeyItem(id: 4, key: UUID().uuidString),
|
||||
]
|
||||
@EnvironmentObject private var store: AppState
|
||||
var body: some View {
|
||||
BaseNavigationView(title: "Key") {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .center) {
|
||||
ForEach(keyItems) { keyItem in
|
||||
ForEach(store.keys.reversed()) { keyItem in
|
||||
DeletableView(contentView: {
|
||||
CardView {
|
||||
KeyItemView(keyItem: keyItem)
|
||||
}
|
||||
|
||||
}, deleteAction: {
|
||||
keyItems.removeAll { _keyItem in
|
||||
store.keys.removeAll { _keyItem in
|
||||
keyItem.id == _keyItem.id
|
||||
}
|
||||
HToast.showSuccess("已删除")
|
||||
Task {
|
||||
do {
|
||||
_ = try await HttpRequest.rmKey(id: keyItem.id)
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
.padding(EdgeInsets(top: 18, leading: 26, bottom: 0, trailing: 24))
|
||||
}
|
||||
@ -36,20 +39,26 @@ struct KeyListView: View {
|
||||
}
|
||||
}
|
||||
.navigationBarItems(trailing: Button(action: {
|
||||
let keyItem = KeyItem(id: Int(arc4random_uniform(1000)), key: UUID().uuidString)
|
||||
Task {
|
||||
let keys = try await HttpRequest.genKey().keys
|
||||
withAnimation(.easeOut) {
|
||||
keyItems.insert(keyItem, at: 0)
|
||||
store.keys = keys
|
||||
}
|
||||
HToast.showSuccess("已添加新Key")
|
||||
}
|
||||
}, label: {
|
||||
Image(systemName: "plus")
|
||||
.foregroundColor(Color(UIColor.lightGray))
|
||||
}))
|
||||
}
|
||||
.onAppear {
|
||||
HttpRequest.loadKeys()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
KeyListView()
|
||||
KeyListView().environmentObject(AppState.shared)
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,10 @@ import AuthenticationServices
|
||||
|
||||
/// 登录界面
|
||||
struct LoginView: View {
|
||||
|
||||
@EnvironmentObject private var store: AppState
|
||||
@State private var showLoading = false
|
||||
|
||||
var body: some View {
|
||||
VStack{
|
||||
Spacer()
|
||||
@ -17,40 +21,28 @@ struct LoginView: View {
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
Spacer()
|
||||
if showLoading {
|
||||
ProgressView()
|
||||
.scaleEffect(1.5)
|
||||
.frame(height: 64)
|
||||
} else {
|
||||
AppleSignInButton(
|
||||
onRequest: { request in
|
||||
request.requestedScopes = [.fullName, .email]
|
||||
},
|
||||
onCompletion: { result in
|
||||
switch result {
|
||||
case let .success(authorization):
|
||||
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
|
||||
// 用户唯一ID,在一个开发者账号下的APP获取到的是一样的
|
||||
print(appleIDCredential.user) // 000791.7a323f1326dd4674bc16d32fd6339875.1424
|
||||
// 注意:当第一次认证成功之后,将不会再返回email,fullName等信息,可以在设置->Apple ID->密码与安全性->使用您AppleID的App 中删除对应的APP。
|
||||
print(appleIDCredential.email as Any) // easychen@qq.com
|
||||
print(appleIDCredential.fullName as Any) // givenName: lijie familyName: chen
|
||||
// 「JWT」格式的token,用于验证信息合法性。其值用.分割成3段, 中间一段base64后会看到包含了 用户唯一标识 和 邮箱 等字段
|
||||
let idToken = String(data:appleIDCredential.identityToken!, encoding: .utf8)
|
||||
print(idToken as Any)
|
||||
|
||||
Task {
|
||||
do {
|
||||
let result = try await HttpRequest.login(idToken: idToken!)
|
||||
|
||||
print(result)
|
||||
showLoading = true
|
||||
store.token = try await store.appleIdLogin(result).token
|
||||
// 获取成功去主页
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .failure(error):
|
||||
print(error)
|
||||
showLoading = false
|
||||
}
|
||||
}
|
||||
)
|
||||
.frame(maxWidth: 375, minHeight: 64, maxHeight: 64)
|
||||
.padding()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
|
@ -9,32 +9,38 @@ import SwiftUI
|
||||
|
||||
/// APP 主界面
|
||||
struct MainView: View {
|
||||
@EnvironmentObject private var store: AppState
|
||||
|
||||
var body: some View {
|
||||
TabView {
|
||||
TabView.init(selection: $store.tabSelectedIndex) {
|
||||
DeviceListView()
|
||||
.tabItem {
|
||||
Label("设备",systemImage: "ipad.and.iphone")
|
||||
}
|
||||
.tag(0)
|
||||
|
||||
KeyListView()
|
||||
.tabItem{
|
||||
Label("Key",systemImage: "key")
|
||||
}
|
||||
.tag(1)
|
||||
|
||||
MessageListView()
|
||||
.tabItem({Label("消息",systemImage: "message")}).onTapGesture {
|
||||
}
|
||||
.tag(2)
|
||||
|
||||
SettingsView()
|
||||
.tabItem{
|
||||
Label("设置",systemImage: "gearshape")
|
||||
}
|
||||
.tag(3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MainView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
MainView()
|
||||
MainView().environmentObject(AppState.shared)
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,12 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import MarkdownUI
|
||||
import SDWebImageSwiftUI
|
||||
import Photos
|
||||
|
||||
struct MessageItemView: View {
|
||||
let messageItem: MessageItem
|
||||
/// 删除按钮点击的回调
|
||||
let deleteAction : () -> ()
|
||||
|
||||
@ -23,7 +27,7 @@ struct MessageItemView: View {
|
||||
Text("key名字")
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(Color(UIColor.darkGray))
|
||||
Text("· 5分钟前")
|
||||
Text(messageItem.createdDateStr)
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(Color(UIColor.darkGray))
|
||||
HLine().stroke(Color(UIColor.lightGray))
|
||||
@ -31,33 +35,122 @@ struct MessageItemView: View {
|
||||
}
|
||||
|
||||
DeletableView(contentView: {
|
||||
CardView {
|
||||
HStack{
|
||||
Text("纯文本的效果")
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(Color(UIColor.darkGray))
|
||||
.padding()
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.contextMenu {
|
||||
Button("复制") {
|
||||
UIPasteboard.general.string = "someText"
|
||||
}
|
||||
}
|
||||
}
|
||||
MessageContentView(messageItem: messageItem)
|
||||
}, deleteAction: deleteAction)
|
||||
.padding(EdgeInsets(top: 10, leading: 26, bottom: 0, trailing: 24))
|
||||
.padding(messageItem.type == "image" ? EdgeInsets.init() : EdgeInsets(top: 10, leading: 26, bottom: 0, trailing: 24))
|
||||
|
||||
}
|
||||
.padding(.top, 25)
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageContentView: View {
|
||||
let messageItem: MessageItem
|
||||
@State private var image: PlatformImage? = nil
|
||||
|
||||
var body: some View {
|
||||
switch messageItem.type {
|
||||
case "markdown":
|
||||
CardView {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
Markdown(Document(messageItem.text))
|
||||
.markdownStyle(
|
||||
DefaultMarkdownStyle(
|
||||
font: .system(size: 14),
|
||||
foregroundColor: UIColor.darkGray
|
||||
)
|
||||
)
|
||||
if !messageItem.desp.isEmpty {
|
||||
Markdown(Document(messageItem.desp))
|
||||
.markdownStyle(
|
||||
DefaultMarkdownStyle(
|
||||
font: .system(size: 14),
|
||||
foregroundColor: UIColor.darkGray
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
case "image":
|
||||
WebImage(url: URL(string: messageItem.text))
|
||||
.onSuccess { image, data, cacheType in
|
||||
self.image = image
|
||||
}
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.contextMenu {
|
||||
Button {
|
||||
UIPasteboard.general.image = image
|
||||
HToast.showSuccess("已拷贝")
|
||||
} label: {
|
||||
Label("拷贝图片",systemImage: "doc.on.doc")
|
||||
}
|
||||
Button {
|
||||
guard let image = image else { return }
|
||||
PHPhotoLibrary.shared().performChanges {
|
||||
PHAssetChangeRequest.creationRequestForAsset(from: image)
|
||||
} completionHandler: { (isSuccess, error) in
|
||||
DispatchQueue.main.async {
|
||||
if isSuccess {// 成功
|
||||
print("Success")
|
||||
HToast.showSuccess("保存成功")
|
||||
} else {
|
||||
print(error as Any)
|
||||
HToast.showError("保存失败")
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Label("保存图片",systemImage: "square.and.arrow.down")
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
CardView {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
HStack{
|
||||
Text(messageItem.text)
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(Color(UIColor.darkGray))
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
if !messageItem.desp.isEmpty {
|
||||
Text(messageItem.desp)
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(Color(UIColor.darkGray))
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.contextMenu {
|
||||
Button {
|
||||
UIPasteboard.general.string = messageItem.text + messageItem.desp
|
||||
HToast.showSuccess("已复制")
|
||||
} label: {
|
||||
Label("复制",systemImage: "doc.on.doc")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageItemView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
MessageItemView(){}
|
||||
MessageItemView(){}
|
||||
MessageItemView(messageItem: MessageItem(id: 1, uid: "1", text: "纯文本的效果", desp: "你好呀", type: "text", created_at: "2022-01-08T18:00:48.000000Z")){}
|
||||
MessageItemView(messageItem: MessageItem(id: 1, uid: "1", text: "纯文本的效果纯文本的效果纯文本的效果纯文本的效果纯文本的效果纯文本的效果纯文本的效果", desp: "", type: "text", created_at: "2022-01-08T18:00:48.000000Z")){}
|
||||
MessageItemView(messageItem: MessageItem(id: 1, uid: "1", text: "https://blog.wskfz.com/usr/uploads/2018/06/2498727457.png", desp: "", type: "image", created_at: "2022-01-08T18:00:48.000000Z")){}
|
||||
MessageItemView(messageItem: MessageItem(id: 1, uid: "1", text: "https://blog.wskfz.com/usr/uploads/2018/06/2151130181.png", desp: "", type: "image", created_at: "2022-01-08T18:00:48.000000Z")){}
|
||||
MessageItemView(messageItem: MessageItem(id: 1, uid: "1", text: "https://blog.wskfz.com/usr/uploads/2018/06/1718629805.png", desp: "", type: "image", created_at: "2022-01-08T18:00:48.000000Z")){}
|
||||
MessageItemView(messageItem: MessageItem(id: 1, uid: "1", text: "*MarkDown*的**效果**", desp: "*MarkDown*的**效果**", type: "markdown", created_at: "2021-12-28T13:44:48.000000Z")){}
|
||||
MessageItemView(messageItem: MessageItem(id: 1, uid: "1", text: """
|
||||
It's very easy to make some words **bold** and other words *italic* with Markdown.
|
||||
|
||||
**Want to experiment with Markdown?** Play with the [reference CommonMark
|
||||
implementation](https://spec.commonmark.org/dingus/).
|
||||
""", desp: "", type: "markdown", created_at: "2021-12-28T13:44:48.000000Z")){}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
@ -9,21 +9,27 @@ import SwiftUI
|
||||
|
||||
/// 消息界面
|
||||
struct MessageListView: View {
|
||||
|
||||
@State private var messages = Array(0..<10)
|
||||
@State private var isShowTest = true
|
||||
@EnvironmentObject private var store: AppState
|
||||
|
||||
var body: some View {
|
||||
BaseNavigationView(title: "消息") {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading) {
|
||||
if isShowTest {
|
||||
if store.isShowTestPush {
|
||||
TestPushView()
|
||||
}
|
||||
ForEach(messages, id: \.self) { msg in
|
||||
MessageItemView {
|
||||
messages.removeAll { _msg in
|
||||
_msg == msg
|
||||
ForEach(store.messages) { messageItem in
|
||||
MessageItemView(messageItem: messageItem) {
|
||||
store.messages.removeAll { _messageItem in
|
||||
_messageItem.id == messageItem.id
|
||||
}
|
||||
HToast.showSuccess("已删除")
|
||||
Task {
|
||||
do {
|
||||
_ = try await HttpRequest.rmMessage(id: messageItem.id)
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -32,17 +38,23 @@ struct MessageListView: View {
|
||||
}
|
||||
.navigationBarItems(trailing: Button(action: {
|
||||
withAnimation(.easeOut) {
|
||||
isShowTest = !isShowTest
|
||||
store.isShowTestPush = !store.isShowTestPush
|
||||
}
|
||||
}, label: {
|
||||
Image(systemName: isShowTest ? "chevron.up" : "chevron.down")
|
||||
Image(systemName: store.isShowTestPush ? "chevron.up" : "chevron.down")
|
||||
.foregroundColor(Color(UIColor.lightGray))
|
||||
}))
|
||||
}
|
||||
.onAppear {
|
||||
Task {
|
||||
store.messages = try await HttpRequest.getMessages().messages
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TestPushView: View {
|
||||
@EnvironmentObject private var store: AppState
|
||||
@State private var testText = ""
|
||||
var body: some View {
|
||||
TextEditor(text: $testText)
|
||||
@ -52,6 +64,26 @@ struct TestPushView: View {
|
||||
|
||||
Button("推送测试") {
|
||||
print("点击推送测试")
|
||||
if testText.isEmpty {
|
||||
HToast.showError("推送失败, 请先输入推送内容")
|
||||
return
|
||||
}
|
||||
Task {
|
||||
if store.keys.isEmpty {
|
||||
store.keys = try await HttpRequest.getKeys().keys
|
||||
}
|
||||
if let keyItem = store.keys.first {
|
||||
_ = try await HttpRequest.push(pushkey: keyItem.key, text: testText, desp: "", type: "")
|
||||
testText = ""
|
||||
HToast.showSuccess("推送成功")
|
||||
let messages = try await HttpRequest.getMessages().messages
|
||||
withAnimation(.easeOut) {
|
||||
store.messages = messages
|
||||
}
|
||||
} else {
|
||||
HToast.showError("推送失败, 请先添加一个Key")
|
||||
}
|
||||
}
|
||||
}
|
||||
.font(.system(size: 20))
|
||||
.frame(width: 104, height: 42)
|
||||
|
@ -18,8 +18,8 @@ struct SettingsItemView: View {
|
||||
Text(title)
|
||||
.font(.system(size: 18))
|
||||
.foregroundColor(Color(UIColor.darkGray))
|
||||
.padding()
|
||||
Spacer()
|
||||
.padding(.leading, 16)
|
||||
Spacer(minLength: 0)
|
||||
Button(button) {
|
||||
print("点击\(button)")
|
||||
action()
|
||||
|
@ -6,32 +6,49 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
//import StoreKit
|
||||
|
||||
/// 设置界面
|
||||
struct SettingsView: View {
|
||||
@EnvironmentObject private var store: AppState
|
||||
|
||||
var body: some View {
|
||||
BaseNavigationView(title: "设置") {
|
||||
VStack {
|
||||
SettingsItemView(title: "登录为 Hext", button: "退出") {
|
||||
SettingsItemView(title: "登录为 \(store.userInfo?.name ?? "--")", button: "退出") {
|
||||
store.token = ""
|
||||
}
|
||||
.padding(EdgeInsets(top: 18, leading: 20, bottom: 0, trailing: 20))
|
||||
|
||||
SettingsItemView(title: "自定义服务器", button: "扫码") {
|
||||
}
|
||||
.disabled(true)
|
||||
.padding(EdgeInsets(top: 18, leading: 20, bottom: 0, trailing: 20))
|
||||
|
||||
SettingsItemView(title: "喜欢PushDeer?", button: "评分") {
|
||||
let urlStr = "itms-apps://itunes.apple.com/app/id\(1596771139)?action=write-review"
|
||||
UIApplication.shared.open(URL(string: urlStr)!, options: [:], completionHandler: nil)
|
||||
// 直接弹出系统评分控件, 不过一年最多3次, 用户还可以在系统设置里面关
|
||||
// SKStoreReviewController.requestReview()
|
||||
}
|
||||
.padding(EdgeInsets(top: 18, leading: 20, bottom: 0, trailing: 20))
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if store.userInfo != nil {
|
||||
return
|
||||
}
|
||||
Task {
|
||||
store.userInfo = try await HttpRequest.getUserInfo()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsView()
|
||||
SettingsView().environmentObject(AppState.shared)
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// PushDeerClip
|
||||
//
|
||||
// Created by HEXT on 2021/12/30.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
var body: some View {
|
||||
MainView()
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
}
|
||||
}
|
@ -2,11 +2,6 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSAppClip</key>
|
||||
<dict>
|
||||
<key>NSAppClipRequestEphemeralUserNotification</key>
|
||||
@ -14,5 +9,20 @@
|
||||
<key>NSAppClipRequestLocationConfirmation</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_adhp._tcp</string>
|
||||
</array>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>APP需要访问本地网络以供调试(仅在开发时)</string>
|
||||
<key>CFBundleAllowMixedLocalizations</key>
|
||||
<true/>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>APP为您提供保存图片到相册的功能</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
Loading…
Reference in New Issue
Block a user