diff --git a/Podfile b/Podfile index 016e1fc..fdf432b 100644 --- a/Podfile +++ b/Podfile @@ -33,5 +33,6 @@ target 'ReaderHive' do pod 'Toast' pod 'SVProgressHUD' pod 'FDFullscreenPopGesture' + pod 'ZLPhotoBrowser' end diff --git a/ReaderHive.xcodeproj/project.pbxproj b/ReaderHive.xcodeproj/project.pbxproj index b316ccc..377bc9d 100644 --- a/ReaderHive.xcodeproj/project.pbxproj +++ b/ReaderHive.xcodeproj/project.pbxproj @@ -96,6 +96,12 @@ 85CF94292EED4664006467E3 /* NRVipRetainAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CF94282EED4664006467E3 /* NRVipRetainAlert.swift */; }; 85CF942B2EED47F2006467E3 /* NRVipRetainItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CF942A2EED47F2006467E3 /* NRVipRetainItemView.swift */; }; 85CF942D2EED5204006467E3 /* NRPayAlertModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CF942C2EED5204006467E3 /* NRPayAlertModel.swift */; }; + 85CF942F2EEFB2CF006467E3 /* NRLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CF942E2EEFB2CF006467E3 /* NRLoginView.swift */; }; + 85CF94312EEFDA88006467E3 /* NRLoginManager+Apple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CF94302EEFDA82006467E3 /* NRLoginManager+Apple.swift */; }; + 85CF94332EEFDAF4006467E3 /* NRThirdSignModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CF94322EEFDAF4006467E3 /* NRThirdSignModel.swift */; }; + 85CF94372EEFE27E006467E3 /* FacebookLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 85CF94362EEFE27E006467E3 /* FacebookLogin */; }; + 85CF94392EEFE35A006467E3 /* NRLoginManager+Facebook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CF94382EEFE34F006467E3 /* NRLoginManager+Facebook.swift */; }; + 85CF943B2EEFE94C006467E3 /* AppDelegate+Open.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CF943A2EEFE947006467E3 /* AppDelegate+Open.swift */; }; F34348AF2ED5B85300AA7E70 /* NRExploreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348AE2ED5B85300AA7E70 /* NRExploreViewController.swift */; }; F34348B12ED5B9A400AA7E70 /* NRExploreNovelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348B02ED5B9A400AA7E70 /* NRExploreNovelViewController.swift */; }; F34348B32ED5BB6100AA7E70 /* NRExploreNovelMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348B22ED5BB6100AA7E70 /* NRExploreNovelMenuView.swift */; }; @@ -327,6 +333,12 @@ 85CF94282EED4664006467E3 /* NRVipRetainAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRVipRetainAlert.swift; sourceTree = ""; }; 85CF942A2EED47F2006467E3 /* NRVipRetainItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRVipRetainItemView.swift; sourceTree = ""; }; 85CF942C2EED5204006467E3 /* NRPayAlertModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRPayAlertModel.swift; sourceTree = ""; }; + 85CF942E2EEFB2CF006467E3 /* NRLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRLoginView.swift; sourceTree = ""; }; + 85CF94302EEFDA82006467E3 /* NRLoginManager+Apple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NRLoginManager+Apple.swift"; sourceTree = ""; }; + 85CF94322EEFDAF4006467E3 /* NRThirdSignModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRThirdSignModel.swift; sourceTree = ""; }; + 85CF94342EEFDFAC006467E3 /* ReaderHive.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ReaderHive.entitlements; sourceTree = ""; }; + 85CF94382EEFE34F006467E3 /* NRLoginManager+Facebook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NRLoginManager+Facebook.swift"; sourceTree = ""; }; + 85CF943A2EEFE947006467E3 /* AppDelegate+Open.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Open.swift"; sourceTree = ""; }; C3BEE224CB3F55939653D26D /* Pods-NovelReader.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NovelReader.debug.xcconfig"; path = "Target Support Files/Pods-NovelReader/Pods-NovelReader.debug.xcconfig"; sourceTree = ""; }; F34348AE2ED5B85300AA7E70 /* NRExploreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRExploreViewController.swift; sourceTree = ""; }; F34348B02ED5B9A400AA7E70 /* NRExploreNovelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRExploreNovelViewController.swift; sourceTree = ""; }; @@ -469,6 +481,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 85CF94372EEFE27E006467E3 /* FacebookLogin in Frameworks */, 67DC33BD353DB9F2D4C0FFE8 /* Pods_ReaderHive.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -588,6 +601,7 @@ 03980F862ED009EB0006E317 /* ReaderHive */ = { isa = PBXGroup; children = ( + 85CF94342EEFDFAC006467E3 /* ReaderHive.entitlements */, 03980F8F2ED00ACD0006E317 /* Delegate */, 039810562ED046030006E317 /* Base */, 039810752ED054090006E317 /* Class */, @@ -615,6 +629,7 @@ 03980F7D2ED009EB0006E317 /* AppDelegate.swift */, 03980F842ED009EB0006E317 /* SceneDelegate.swift */, 0398107B2ED0551C0006E317 /* AppDelegate+Config.swift */, + 85CF943A2EEFE947006467E3 /* AppDelegate+Open.swift */, ); path = Delegate; sourceTree = ""; @@ -728,8 +743,11 @@ isa = PBXGroup; children = ( 0398107E2ED055D10006E317 /* NRLoginManager.swift */, + 85CF94302EEFDA82006467E3 /* NRLoginManager+Apple.swift */, + 85CF94382EEFE34F006467E3 /* NRLoginManager+Facebook.swift */, 039810802ED056090006E317 /* NRLoginToken.swift */, 039810822ED0563D0006E317 /* NRUserInfo.swift */, + 85CF94322EEFDAF4006467E3 /* NRThirdSignModel.swift */, ); path = Login; sourceTree = ""; @@ -1027,6 +1045,7 @@ F349910A2EE16B520039E939 /* NRAboutCell.swift */, F34991172EE1780A0039E939 /* NRNovelHistoryCell.swift */, F3B859382EE676610095A9CC /* NRLanguageCell.swift */, + 85CF942E2EEFB2CF006467E3 /* NRLoginView.swift */, ); path = V; sourceTree = ""; @@ -1222,6 +1241,9 @@ ); mainGroup = 03980F5C2ED009E30006E317; minimizedProjectReferenceProxies = 1; + packageReferences = ( + 85CF94352EEFE27E006467E3 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */, + ); preferredProjectObjectVersion = 77; productRefGroup = 03980F662ED009E30006E317 /* Products */; projectDirPath = ""; @@ -1299,9 +1321,11 @@ files = ( F34991252EE27DB60039E939 /* NRGradientButton.swift in Sources */, F34348FD2ED8561300AA7E70 /* NRNovelReadContentBottomView.swift in Sources */, + 85CF94332EEFDAF4006467E3 /* NRThirdSignModel.swift in Sources */, F34348C52ED6CA4D00AA7E70 /* NRExploreNovelMenuDataSource.swift in Sources */, F343492A2EDD93AD00AA7E70 /* NRReadSettingFontView.swift in Sources */, 039810982ED066B20006E317 /* NRTool.swift in Sources */, + 85CF943B2EEFE94C006467E3 /* AppDelegate+Open.swift in Sources */, F34991272EE2826D0039E939 /* NRNovelReadViewModel+Data.swift in Sources */, F34348CD2ED6DD0900AA7E70 /* NRNovelGenresCell.swift in Sources */, F34348FF2ED85BF200AA7E70 /* NRReadBatteryView.swift in Sources */, @@ -1309,6 +1333,7 @@ 039810BA2ED4377E0006E317 /* NRHomeNovelReadWhatCell.swift in Sources */, F3B8596B2EE91C9F0095A9CC /* NRStoreCoinsCell.swift in Sources */, F3B859712EE94A1B0095A9CC /* NRFeedbackViewController.swift in Sources */, + 85CF94392EEFE35A006467E3 /* NRLoginManager+Facebook.swift in Sources */, F34348C72ED6CCBC00AA7E70 /* NRExploreNovelContentListViewController.swift in Sources */, 039810B82ED431780006E317 /* NRHomeNovelReadWhatView.swift in Sources */, F34349142EDA9AE900AA7E70 /* NRNovelReadSettingView.swift in Sources */, @@ -1435,10 +1460,12 @@ F3B859422EE678FB0095A9CC /* NRSettingAPI.swift in Sources */, 0373D9522ED58A950017DCC7 /* NRSearchViewModel.swift in Sources */, F34990BF2EDEDDCF0039E939 /* NRCategoryModel.swift in Sources */, + 85CF942F2EEFB2CF006467E3 /* NRLoginView.swift in Sources */, F34349242EDD3C2400AA7E70 /* NRNovelReadBottomView.swift in Sources */, 039810622ED04F250006E317 /* NRNetwork.swift in Sources */, 039810782ED054740006E317 /* NRHud.swift in Sources */, F34991032EE160F00039E939 /* NRUserAPI.swift in Sources */, + 85CF94312EEFDA88006467E3 /* NRLoginManager+Apple.swift in Sources */, 85606A9F2EEBE95A005D989D /* NRDashedLineView.swift in Sources */, 039810C42ED459440006E317 /* NRHomeNovelHotTagView.swift in Sources */, F34991122EE170E20039E939 /* NRWebView.swift in Sources */, @@ -1546,6 +1573,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = ReaderHive/ReaderHive.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -1555,6 +1583,9 @@ INFOPLIST_FILE = ReaderHive/Source/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ReaderHive; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; + INFOPLIST_KEY_LSApplicationCategoryType = ""; + INFOPLIST_KEY_NSCameraUsageDescription = "The APP needs to access your album to provide screenshots for feedback."; + INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "The APP needs to access your album to provide screenshots for feedback."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = ""; @@ -1587,6 +1618,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = ReaderHive/ReaderHive.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -1595,6 +1627,9 @@ INFOPLIST_FILE = ReaderHive/Source/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ReaderHive; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; + INFOPLIST_KEY_LSApplicationCategoryType = ""; + INFOPLIST_KEY_NSCameraUsageDescription = "The APP needs to access your album to provide screenshots for feedback."; + INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "The APP needs to access your album to provide screenshots for feedback."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = ""; @@ -1764,6 +1799,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 85CF94352EEFE27E006467E3 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/facebook/facebook-ios-sdk"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 14.1.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 85CF94362EEFE27E006467E3 /* FacebookLogin */ = { + isa = XCSwiftPackageProductDependency; + package = 85CF94352EEFE27E006467E3 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */; + productName = FacebookLogin; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 03980F5D2ED009E30006E317 /* Project object */; } diff --git a/ReaderHive/Base/Networking/API/NRUserAPI.swift b/ReaderHive/Base/Networking/API/NRUserAPI.swift index 33696d4..2ae1cd7 100644 --- a/ReaderHive/Base/Networking/API/NRUserAPI.swift +++ b/ReaderHive/Base/Networking/API/NRUserAPI.swift @@ -7,6 +7,7 @@ import UIKit import Alamofire +import SmartCodable struct NRUserAPI { @@ -26,4 +27,35 @@ struct NRUserAPI { } } + + static func requestSignThirdLogin(model: NRThirdSignModel, completer: ((_ token: NRLoginToken?) -> Void)?) { + + var param = NRNetwork.Parameters(path: "/customer/login") + param.method = .post + param.parameters = model.toDictionary() + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in + if response.isSuccess { + completer?(response.data) + } else { + completer?(nil) + } + } + } + + static func requestLogout(completer: ((_ token: NRLoginToken?) -> Void)?) { + var param = NRNetwork.Parameters(path: "/customer/signout") + param.method = .post + param.isLoding = true + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in + if response.isSuccess { + completer?(response.data) + } else { + completer?(nil) + } + } + + } + } diff --git a/ReaderHive/Base/WebView/NRWebViewController+Script.swift b/ReaderHive/Base/WebView/NRWebViewController+Script.swift index a88686c..86858cd 100644 --- a/ReaderHive/Base/WebView/NRWebViewController+Script.swift +++ b/ReaderHive/Base/WebView/NRWebViewController+Script.swift @@ -7,6 +7,7 @@ import UIKit internal import WebKit +import ZLPhotoBrowser ///APP交互 let kNRWebMessageAPP = "js2app" @@ -23,6 +24,76 @@ let kNRWebMessageAccountDeletionFinish = "accountLogout" extension NRWebViewController { func nr_webViewUserContentController(didReceive message: WKScriptMessage) { + let name = message.name + let body = message.body + switch name { + case kNRWebMessageOpenFeedbackList: + let vc = NRAppWebViewController() + vc.webUrl = kNRFeedBackListWebUrl + self.navigationController?.pushViewController(vc, animated: true) + + case kNRWebMessageOpenFeedbackDetail: + guard let body = body as? [String : Any] else { return } + guard let id = body["id"] as? Int else { return } + + let vc = NRAppWebViewController() + vc.id = "\(id)" + vc.webUrl = kNRFeedBackDetailWebUrl + self.navigationController?.pushViewController(vc, animated: true) + + case kNRWebMessageOpenPhotoPicker: + openPhotoPicker() + +// case kNRWebMessageAPP: +// guard let body = message.body as? [String : Any] else { return } +// guard let model = FAWebMessageModel.deserialize(from: body) else { return } +// let type = model.type +// let data = model.data +// +// if type == "login" { +//// let view = FALoginView() +//// view.present(in: nil) +// +// } else { +// +// guard let urlStr = data?.link else { return } +// guard let url = URL(string: urlStr) else { return } +// if UIApplication.shared.canOpenURL(url) { +// UIApplication.shared.open(url) +// } +// } + + case kNRWebMessageAccountDeletionFinish: + self.navigationController?.popToRootViewController(animated: true) + NotificationCenter.default.post(name: NRLoginManager.loginStateDidChangeNotification, object: nil) + + default: + break + } + } + + ///打开相册 + private func openPhotoPicker() { + + ZLPhotoConfiguration.default().allowSelectOriginal = false + ZLPhotoConfiguration.default().maxSelectCount = 1 + ZLPhotoConfiguration.default().allowEditImage = false + ZLPhotoConfiguration.default().allowSelectVideo = false + ZLPhotoConfiguration.default().allowSelectGif = false + ZLPhotoConfiguration.default().allowTakePhotoInLibrary = false + + let picker = ZLPhotoPicker() + picker.selectImageBlock = { [weak self] (results, _) in + guard let self = self else { return } + guard let image = results.first?.image else { return } + guard let imageData = image.jpegData(compressionQuality: 0.8) else { return } + let imageDataStr = imageData.base64EncodedString(options: .endLineWithCarriageReturn) + + let js = "uploadConvertImage('\(imageDataStr)')" + self.webView.evaluateJavaScript(js) + } + + picker.showPhotoLibrary(sender: self) } } diff --git a/ReaderHive/Class/Home/V/NRHomeNovelNewArrivalsCell.swift b/ReaderHive/Class/Home/V/NRHomeNovelNewArrivalsCell.swift index 4ed79a6..e6bbe7b 100644 --- a/ReaderHive/Class/Home/V/NRHomeNovelNewArrivalsCell.swift +++ b/ReaderHive/Class/Home/V/NRHomeNovelNewArrivalsCell.swift @@ -115,6 +115,7 @@ extension NRHomeNovelNewArrivalsCell { categoryView.snp.makeConstraints { make in make.left.equalToSuperview() + make.right.lessThanOrEqualToSuperview() make.top.equalTo(titleLabel.snp.bottom).offset(4) make.height.equalTo(20) } diff --git a/ReaderHive/Class/Me/V/NRLoginView.swift b/ReaderHive/Class/Me/V/NRLoginView.swift new file mode 100644 index 0000000..0f99b6e --- /dev/null +++ b/ReaderHive/Class/Me/V/NRLoginView.swift @@ -0,0 +1,110 @@ +// +// NRLoginView.swift +// ReaderHive +// +// Created by 澜声世纪 on 2025/12/15. +// + +import UIKit +import HWPanModal +import SnapKit + +class NRLoginView: NRPanModalContentView { + + + private lazy var bgView = UIImageView(image: UIImage(named: "bg_image_01")) + + private lazy var titleView = UIImageView(image: UIImage(named: "login_logo_icon")) + + private lazy var appleLoginButton: UIButton = { + var configuration = UIButton.Configuration.plain() + configuration.background.backgroundColor = .F_2_EFEE + configuration.background.cornerRadius = 24 + configuration.image = UIImage(named: "apple_login_logo_icon") + configuration.imagePadding = 8 + configuration.attributedTitle = AttributedString("Login with Apple".localized, attributes: AttributeContainer([ + .font : UIFont.font(ofSize: 14, weight: .medium), + .foregroundColor : UIColor.black + ])) + + let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.login(type: .apple) + })) + return button + }() + + private lazy var facebookLoginButton: UIButton = { + var configuration = UIButton.Configuration.plain() + configuration.background.backgroundColor = .F_2_EFEE + configuration.background.cornerRadius = 24 + configuration.image = UIImage(named: "facebook_login_logo_icon") + configuration.imagePadding = 8 + configuration.attributedTitle = AttributedString("Login with Facebook".localized, attributes: AttributeContainer([ + .font : UIFont.font(ofSize: 14, weight: .medium), + .foregroundColor : UIColor.black + ])) + + let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.login(type: .faceBook) + })) + return button + }() + + override init(frame: CGRect) { + super.init(frame: frame) + contentHeight = 220 + UIScreen.safeBottom + nr_setupUI() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func login(type: NRLoginManager.LoginType) { + NRHud.show() + NRLoginManager.manager.thirdLogin(type: type, presentingViewController: nil) { [weak self] isFinish in + NRHud.dismiss() + guard let self = self else { return } + guard isFinish else { return } + self.dismiss(animated: true) { + + } + } + + } + +} + +extension NRLoginView { + + private func nr_setupUI() { + addSubview(bgView) + addSubview(titleView) + addSubview(appleLoginButton) + addSubview(facebookLoginButton) + + bgView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + } + + titleView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(24) + } + + appleLoginButton.snp.makeConstraints { make in + make.left.equalToSuperview().offset(44) + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(84) + make.height.equalTo(48) + } + + facebookLoginButton.snp.makeConstraints { make in + make.left.right.height.equalTo(appleLoginButton) + make.top.equalTo(appleLoginButton.snp.bottom).offset(16) + } + } + +} diff --git a/ReaderHive/Class/Me/V/NRMeHeaderView.swift b/ReaderHive/Class/Me/V/NRMeHeaderView.swift index 7f547fa..a46f8f8 100644 --- a/ReaderHive/Class/Me/V/NRMeHeaderView.swift +++ b/ReaderHive/Class/Me/V/NRMeHeaderView.swift @@ -8,6 +8,7 @@ import UIKit import SnapKit import YYCategories +import HWPanModal class NRMeHeaderView: UITableViewHeaderFooterView { @@ -21,6 +22,7 @@ class NRMeHeaderView: UITableViewHeaderFooterView { coinsView.userInfo = userInfo + loginButton.isHidden = !(userInfo?.is_tourist ?? false) stackView.nr_removeAllArrangedSubview() stackView.addArrangedSubview(coinsView) @@ -72,6 +74,16 @@ class NRMeHeaderView: UITableViewHeaderFooterView { return button }() + private lazy var loginButton: UIButton = { + let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + let view = NRLoginView() + view.present(in: nil) + })) + button.setImage(UIImage(named: "login_button"), for: .normal) + return button + }() + private lazy var stackView: UIStackView = { let view = UIStackView() view.axis = .vertical @@ -121,6 +133,7 @@ extension NRMeHeaderView { contentView.addSubview(idBgView) idBgView.addSubview(idLabel) idBgView.addSubview(copyButton) + contentView.addSubview(loginButton) contentView.addSubview(stackView) avatarImageView.snp.makeConstraints { make in @@ -152,6 +165,11 @@ extension NRMeHeaderView { make.left.equalTo(idLabel.snp.right).offset(8) } + loginButton.snp.makeConstraints { make in + make.centerY.equalTo(avatarImageView) + make.right.equalToSuperview().offset(-16) + } + stackView.snp.makeConstraints { make in make.left.right.equalToSuperview() make.top.equalTo(avatarImageView.snp.bottom).offset(16) diff --git a/ReaderHive/Class/Me/VC/NRFeedbackViewController.swift b/ReaderHive/Class/Me/VC/NRFeedbackViewController.swift index e73c2d3..3be2d73 100644 --- a/ReaderHive/Class/Me/VC/NRFeedbackViewController.swift +++ b/ReaderHive/Class/Me/VC/NRFeedbackViewController.swift @@ -13,18 +13,14 @@ class NRFeedbackViewController: NRAppWebViewController { self.webUrl = kNRFeedBackHomeWebUrl super.viewDidLoad() + self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "feedback_list_icon_01"), style: .plain, target: self, action: #selector(openFeedbackList)) } - - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. + @objc private func openFeedbackList() { + let vc = NRAppWebViewController() + vc.webUrl = kNRFeedBackListWebUrl + self.navigationController?.pushViewController(vc, animated: true) } - */ } diff --git a/ReaderHive/Delegate/AppDelegate+Config.swift b/ReaderHive/Delegate/AppDelegate+Config.swift index 2399ea4..18b4e0b 100644 --- a/ReaderHive/Delegate/AppDelegate+Config.swift +++ b/ReaderHive/Delegate/AppDelegate+Config.swift @@ -8,6 +8,7 @@ import UIKit import IQKeyboardManagerSwift import IQKeyboardToolbarManager import MJRefresh +import FacebookCore extension AppDelegate { @@ -30,4 +31,9 @@ extension AppDelegate { UIDevice.current.isBatteryMonitoringEnabled = true } + + func nr_registThirdparty(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) { + ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions) + } + } diff --git a/ReaderHive/Delegate/AppDelegate+Open.swift b/ReaderHive/Delegate/AppDelegate+Open.swift new file mode 100644 index 0000000..4bd1c89 --- /dev/null +++ b/ReaderHive/Delegate/AppDelegate+Open.swift @@ -0,0 +1,33 @@ +// +// AppDelegate+Open.swift +// ReaderHive +// +// Created by 澜声世纪 on 2025/12/15. +// + +import UIKit +import FacebookCore + +extension SceneDelegate { + + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + guard let url = URLContexts.first?.url else { + return + } + var result = false + + result = ApplicationDelegate.shared.application(UIApplication.shared, open: url, sourceApplication: nil, annotation: [UIApplication.OpenURLOptionsKey.annotation]) + + + } + + func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + guard let webpageURL = userActivity.webpageURL else { return } + var result = false + + result = ApplicationDelegate.shared.application(UIApplication.shared, continue: userActivity) + + + } + +} diff --git a/ReaderHive/Delegate/AppDelegate.swift b/ReaderHive/Delegate/AppDelegate.swift index 08714c9..7ec2e35 100644 --- a/ReaderHive/Delegate/AppDelegate.swift +++ b/ReaderHive/Delegate/AppDelegate.swift @@ -6,15 +6,16 @@ // import UIKit +import FacebookCore @main class AppDelegate: UIResponder, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { NRTool.appDelegate = self NRNetworkReachableManager.manager.startMonitoring() + nr_registThirdparty(application, didFinishLaunchingWithOptions: launchOptions) NotificationCenter.default.addObserver(self, selector: #selector(networkStatusDidChangeNotification), name: NRNetworkReachableManager.networkStatusDidChangeNotification, object: nil) diff --git a/ReaderHive/Libs/Login/NRLoginManager+Apple.swift b/ReaderHive/Libs/Login/NRLoginManager+Apple.swift new file mode 100644 index 0000000..ae97453 --- /dev/null +++ b/ReaderHive/Libs/Login/NRLoginManager+Apple.swift @@ -0,0 +1,107 @@ +// +// NRLoginManager+Apple.swift +// ReaderHive +// +// Created by 澜声世纪 on 2025/12/15. +// + +import UIKit +import AuthenticationServices + +extension NRLoginManager { + + private struct AssociatedKeys { + static var appleLoginHandle: Int? + } + + private var appleLoginHandle: ((_ model: NRThirdSignModel?) -> Void)? { + set { + objc_setAssociatedObject(self, &AssociatedKeys.appleLoginHandle, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) + } + get { + return objc_getAssociatedObject(self, &AssociatedKeys.appleLoginHandle) as? ((_ model: NRThirdSignModel?) -> Void) + } + } + + func nr_appleLogin(completer: ((_ model: NRThirdSignModel?) -> Void)?) { + self.appleLoginHandle = completer + + let appleIDProvider = ASAuthorizationAppleIDProvider() + let request = appleIDProvider.createRequest() + request.requestedScopes = [.fullName, .email] + + let authorizationController = ASAuthorizationController(authorizationRequests: [request]) + authorizationController.delegate = self + authorizationController.presentationContextProvider = self + authorizationController.performRequests() + } + + private func jwtDecode(jwtStr: String) -> [String: Any]? { + let segments = jwtStr.components(separatedBy: ".") + guard segments.count > 1 else { return nil } + + var base64String = segments[1] + + // 处理 Base64 补齐 + let requiredLength = 4 * Int(ceil(Double(base64String.count) / 4.0)) + let paddingLength = requiredLength - base64String.count + if paddingLength > 0 { + base64String += String(repeating: "=", count: paddingLength) + } + + // 替换 URL 安全字符 + base64String = base64String.replacingOccurrences(of: "-", with: "+") + base64String = base64String.replacingOccurrences(of: "_", with: "/") + + // 解码 Base64 数据 + guard let data = Data(base64Encoded: base64String), + let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []), + let payload = jsonObject as? [String: Any] else { + return nil + } + + return payload + } + +} + +//MARK: ASAuthorizationControllerDelegate +extension NRLoginManager: ASAuthorizationControllerDelegate { + + func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { + if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential { + + let userIdentifier = appleIDCredential.user + let fullName = appleIDCredential.fullName + + let identityToken = appleIDCredential.identityToken.flatMap { String(data: $0, encoding: .utf8) } + let identityTokenParams = self.jwtDecode(jwtStr: identityToken ?? "") + + var model = NRThirdSignModel() + model.platform = .apple + model.third_id = userIdentifier + model.giving_name = fullName?.givenName + model.family_name = fullName?.familyName + model.avator = identityTokenParams?["picture"] as? String + model.email = identityTokenParams?["email"] as? String + + appleLoginHandle?(model) + appleLoginHandle = nil + } + } + + func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { + appleLoginHandle?(nil) + appleLoginHandle = nil + } + +} + +//MARK: ASAuthorizationControllerPresentationContextProviding +extension NRLoginManager: ASAuthorizationControllerPresentationContextProviding { + + func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { + return NRTool.keyWindow! + } + +} diff --git a/ReaderHive/Libs/Login/NRLoginManager+Facebook.swift b/ReaderHive/Libs/Login/NRLoginManager+Facebook.swift new file mode 100644 index 0000000..2eae61b --- /dev/null +++ b/ReaderHive/Libs/Login/NRLoginManager+Facebook.swift @@ -0,0 +1,59 @@ +// +// NRLoginManager+Facebook.swift +// ReaderHive +// +// Created by 澜声世纪 on 2025/12/15. +// + +import UIKit +import FacebookLogin + +extension NRLoginManager { + + func nr_facebookLogin(presentingViewController: UIViewController?, completer: ((_ model: NRThirdSignModel?) -> Void)?) { + + let loginManager = LoginManager() + loginManager.logOut() + loginManager.defaultAudience = .everyone + + loginManager.logIn(permissions: ["public_profile", "email"], from: presentingViewController) { result, error in + guard error == nil, let result = result else { + completer?(nil) + return + } + if result.isCancelled { + completer?(nil) + return + } + let request = GraphRequest(graphPath: "me", parameters: ["fields" : "id,name,email,picture"], httpMethod: .get) + request.start { connection, result, error in + guard let result = result as? [String : Any] else { + completer?(nil) + return + } + var model = NRThirdSignModel() + model.platform = .faceBook + model.third_id = result["id"] as? String + model.email = result["email"] as? String + + if let picture = result["picture"] as? [String : Any], + let data = picture["data"] as? [String : Any], + let url = data["url"] as? String + { + model.avator = url + } + + if let name = result["name"] as? String { + model.family_name = name + } else { + model.family_name = result["first_name"] as? String + model.giving_name = result["last_name"] as? String + } + completer?(model) + } + + + } + } + +} diff --git a/ReaderHive/Libs/Login/NRLoginManager.swift b/ReaderHive/Libs/Login/NRLoginManager.swift index 74b770b..394eda2 100644 --- a/ReaderHive/Libs/Login/NRLoginManager.swift +++ b/ReaderHive/Libs/Login/NRLoginManager.swift @@ -6,9 +6,17 @@ // import UIKit +import SmartCodable class NRLoginManager: NSObject { + enum LoginType: String, SmartCaseDefaultable { + case apple = "Apple" + case faceBook = "Facebook" + case google = "Google" + case tiktok = "Tiktok" + } + static let manager = NRLoginManager() private(set) var token = UserDefaults.nr_object(forKey: kNRLoginTokenDefaultsKey, as: NRLoginToken.self) @@ -30,6 +38,72 @@ class NRLoginManager: NSObject { } } + ///第三方登录 + func thirdLogin(type: LoginType, presentingViewController: UIViewController?, completer: ((_ isFinish: Bool) -> Void)?) { + switch type { + case .apple: + nr_appleLogin { [weak self] model in + self?.requestSignThirdLogin(thirdSignModel: model, completer: completer) + } + + case .faceBook: + nr_facebookLogin(presentingViewController: presentingViewController) { [weak self] model in + self?.requestSignThirdLogin(thirdSignModel: model, completer: completer) + } + + default: + break + } + } + + ///后台验证三方登录 + private func requestSignThirdLogin(thirdSignModel: NRThirdSignModel?, completer: ((_ isFinish: Bool) -> Void)?) { + guard let thirdSignModel = thirdSignModel else { + completer?(false) + return + } +// FAStatAPI.requestLeaveApp() + NRUserAPI.requestSignThirdLogin(model: thirdSignModel) { [weak self] token in + guard let self = self else { return } + guard let token = token else { + completer?(false) + return + } + self.setAccountToken(token) + self.userInfo?.is_tourist = false + Task { + await self.updateUserInfo() + } +// FAStatAPI.requestEnterApp() +// FAStatAPI.requestStatOnLine() + completer?(true) + NotificationCenter.default.post(name: NRLoginManager.userInfoUpdateNotification, object: nil) + NotificationCenter.default.post(name: NRLoginManager.loginStateDidChangeNotification, object: nil) + } + } + + func logout(completer: ((_ isFinish: Bool) -> Void)?) { +// FAStatAPI.requestLeaveApp() + + NRUserAPI.requestLogout { [weak self] token in + guard let self = self else { return } + if let token = token { + self.setAccountToken(token) + self.userInfo?.is_tourist = true + Task { + await self.updateUserInfo() + } +// FAStatAPI.requestEnterApp() +// FAStatAPI.requestStatOnLine() + completer?(true) + NotificationCenter.default.post(name: NRLoginManager.userInfoUpdateNotification, object: nil) + NotificationCenter.default.post(name: NRLoginManager.loginStateDidChangeNotification, object: nil) + } else { + completer?(false) + } + } + + } } diff --git a/ReaderHive/Libs/Login/NRThirdSignModel.swift b/ReaderHive/Libs/Login/NRThirdSignModel.swift new file mode 100644 index 0000000..f0180c3 --- /dev/null +++ b/ReaderHive/Libs/Login/NRThirdSignModel.swift @@ -0,0 +1,24 @@ +// +// NRThirdSignModel.swift +// ReaderHive +// +// Created by 澜声世纪 on 2025/12/15. +// + +import UIKit +import SmartCodable + +struct NRThirdSignModel: SmartCodable { + + var third_id: String? + var email: String? + //姓 + var family_name: String? + //名 + var giving_name: String? + + var avator: String? + + var platform: NRLoginManager.LoginType? + +} diff --git a/ReaderHive/ReaderHive.entitlements b/ReaderHive/ReaderHive.entitlements new file mode 100644 index 0000000..d705386 --- /dev/null +++ b/ReaderHive/ReaderHive.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.developer.applesignin + + Default + + keychain-access-groups + + + diff --git a/ReaderHive/Source/Assets.xcassets/Image/apple_login_logo_icon.imageset/Apple@2x.png b/ReaderHive/Source/Assets.xcassets/Image/apple_login_logo_icon.imageset/Apple@2x.png new file mode 100644 index 0000000..dea1972 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/apple_login_logo_icon.imageset/Apple@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/apple_login_logo_icon.imageset/Apple@3x.png b/ReaderHive/Source/Assets.xcassets/Image/apple_login_logo_icon.imageset/Apple@3x.png new file mode 100644 index 0000000..f7ff555 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/apple_login_logo_icon.imageset/Apple@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/apple_login_logo_icon.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/apple_login_logo_icon.imageset/Contents.json new file mode 100644 index 0000000..e1641ae --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/apple_login_logo_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Apple@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Apple@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/facebook_login_logo_icon.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/facebook_login_logo_icon.imageset/Contents.json new file mode 100644 index 0000000..215149c --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/facebook_login_logo_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Facebook@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Facebook@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/facebook_login_logo_icon.imageset/Facebook@2x.png b/ReaderHive/Source/Assets.xcassets/Image/facebook_login_logo_icon.imageset/Facebook@2x.png new file mode 100644 index 0000000..28337f5 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/facebook_login_logo_icon.imageset/Facebook@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/facebook_login_logo_icon.imageset/Facebook@3x.png b/ReaderHive/Source/Assets.xcassets/Image/facebook_login_logo_icon.imageset/Facebook@3x.png new file mode 100644 index 0000000..fc19fad Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/facebook_login_logo_icon.imageset/Facebook@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/feedback_list_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/feedback_list_icon_01.imageset/Contents.json new file mode 100644 index 0000000..a19a549 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/feedback_list_icon_01.imageset/Contents.json @@ -0,0 +1,20 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/login_button.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/login_button.imageset/Contents.json new file mode 100644 index 0000000..ad8c567 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/login_button.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "登录@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "登录@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/login_button.imageset/登录@2x.png b/ReaderHive/Source/Assets.xcassets/Image/login_button.imageset/登录@2x.png new file mode 100644 index 0000000..396cd8e Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/login_button.imageset/登录@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/login_button.imageset/登录@3x.png b/ReaderHive/Source/Assets.xcassets/Image/login_button.imageset/登录@3x.png new file mode 100644 index 0000000..8163144 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/login_button.imageset/登录@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/login_logo_icon.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/login_logo_icon.imageset/Contents.json new file mode 100644 index 0000000..4ebe70e --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/login_logo_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "文字LOGO(ReaderHive)@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "文字LOGO(ReaderHive)@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/login_logo_icon.imageset/文字LOGO(ReaderHive)@2x.png b/ReaderHive/Source/Assets.xcassets/Image/login_logo_icon.imageset/文字LOGO(ReaderHive)@2x.png new file mode 100644 index 0000000..b3ebfd8 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/login_logo_icon.imageset/文字LOGO(ReaderHive)@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/login_logo_icon.imageset/文字LOGO(ReaderHive)@3x.png b/ReaderHive/Source/Assets.xcassets/Image/login_logo_icon.imageset/文字LOGO(ReaderHive)@3x.png new file mode 100644 index 0000000..c5dc93c Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/login_logo_icon.imageset/文字LOGO(ReaderHive)@3x.png differ diff --git a/ReaderHive/Source/Info.plist b/ReaderHive/Source/Info.plist index 2b56941..a1098e5 100644 --- a/ReaderHive/Source/Info.plist +++ b/ReaderHive/Source/Info.plist @@ -2,6 +2,31 @@ + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + fb1155849519865831 + + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + readerhiveapp + + + + FacebookAppID + 1155849519865831 + FacebookClientToken + 696d03750f4bc7a888c815b071be555d + FacebookDisplayName + $(PRODUCT_NAME) UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/ReaderHive/Source/en.lproj/Localizable.strings b/ReaderHive/Source/en.lproj/Localizable.strings index 4a0dc6c..024d87d 100644 --- a/ReaderHive/Source/en.lproj/Localizable.strings +++ b/ReaderHive/Source/en.lproj/Localizable.strings @@ -129,6 +129,8 @@ "Balance" = "Balance"; "Daily Coins" = "Daily Coins"; "Buy Now" = "Buy Now"; +"Login with Apple" = "Login with Apple"; +"Login with Facebook" = "Login with Facebook"; "retain_alert_text" = "Unlock every show you love!";