diff --git a/remotex-iOS.xcodeproj/project.pbxproj b/remotex-iOS.xcodeproj/project.pbxproj index 491d0e1..5697fbf 100644 --- a/remotex-iOS.xcodeproj/project.pbxproj +++ b/remotex-iOS.xcodeproj/project.pbxproj @@ -64,6 +64,7 @@ 52F7A6F01EB45BF900CF9ADA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 52F7A6EF1EB45BF900CF9ADA /* Assets.xcassets */; }; 52F7A6F31EB45BF900CF9ADA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 52F7A6F11EB45BF900CF9ADA /* LaunchScreen.storyboard */; }; 52F7A6FE1EB45BFA00CF9ADA /* remotex_iOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F7A6FD1EB45BFA00CF9ADA /* remotex_iOSTests.swift */; }; + 7509AB6456865F8A759E2CC1 /* Pods_remotex_iOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BBBDABFBA042E358F89FB56 /* Pods_remotex_iOSTests.framework */; }; DD8C1A9411BF837686EB9EB5 /* Pods_remotex_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D249E964E6ABE362AAAF464 /* Pods_remotex_iOS.framework */; }; /* End PBXBuildFile section */ @@ -152,6 +153,7 @@ 523CA7161EE1074600B8EB20 /* Pods_remotex_iOSTests.framework in Frameworks */, 525B3D1C1EC95487007B5B68 /* AsyncDisplayKit.framework in Frameworks */, 525B3D1A1EC9546D007B5B68 /* XCTest.framework in Frameworks */, + 7509AB6456865F8A759E2CC1 /* Pods_remotex_iOSTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -623,6 +625,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; @@ -674,6 +677,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; @@ -739,6 +743,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "remotex-iOS/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; diff --git a/remotex-iOS/Constants.swift b/remotex-iOS/Constants.swift index 88de7cd..a0537a5 100644 --- a/remotex-iOS/Constants.swift +++ b/remotex-iOS/Constants.swift @@ -52,6 +52,11 @@ struct Constants { static let LogoImage = UIImage(named: "logo")! } + struct AppStore { + static let TitleText = "App Store" + static let ReviewURL = URL(string: "itms-apps://itunes.apple.com/app/id1236035785?action=write-review")! + } + struct MessageDescription { static let NoInternetConnection = "网络连接超时。" } diff --git a/remotex-iOS/Model/AboutModel.swift b/remotex-iOS/Model/AboutModel.swift index ea84d40..3be87c5 100644 --- a/remotex-iOS/Model/AboutModel.swift +++ b/remotex-iOS/Model/AboutModel.swift @@ -13,6 +13,7 @@ struct AboutModel { let slogan: String let description: String let author: String + let review: String let authorTwitterUsername: String let remoteXiOSGitHub: String let remoteXiOSContributors: String @@ -30,7 +31,7 @@ struct AboutModel { self.remoteXiOSContributors = "贡献者列表" self.remoteXiOSGitHub = "GitHub" self.author = "RemoteX iOS App 由 \(self.authorTwitterUsername) 创建,并带领 RemoteX iOS 社区共同开发,详细贡献者信息请点击查看\(self.remoteXiOSContributors)。\n欢迎前往社区 \(self.remoteXiOSGitHub) 主页围观源代码。" - + self.review = "喜欢“RemoteX”吗?\n请前往 \(Constants.AppStore.TitleText) 为我们评分。" self.remoteXWebSite = "https://remotex.ooclab.org" self.remoteXSlack = "https://remotex.slack.com" self.remoteXEmail = "admin@ooclab.org" diff --git a/remotex-iOS/View/AboutNode.swift b/remotex-iOS/View/AboutNode.swift index aa3b5a9..3624baa 100644 --- a/remotex-iOS/View/AboutNode.swift +++ b/remotex-iOS/View/AboutNode.swift @@ -15,6 +15,7 @@ class AboutNode: ASDisplayNode, ASTextNodeDelegate { let scrollNode = ASScrollNode() let descriptionNode = ASTextNode() let authorNode = ASTextNode() + let reviewNode = ASTextNode() let linkInfoNode = ASTextNode() let versionNode = ASTextNode() @@ -34,6 +35,9 @@ class AboutNode: ASDisplayNode, ASTextNodeDelegate { authorNode.attributedText = self.attrStringForAboutAuthor(withSize: Constants.AboutLayout.DescriptionFontSize) authorNode.isUserInteractionEnabled = true authorNode.delegate = self + reviewNode.attributedText = self.attrStringForAboutReview(withSize: Constants.AboutLayout.DescriptionFontSize) + reviewNode.isUserInteractionEnabled = true + reviewNode.delegate = self linkInfoNode.attributedText = self.attrStringForAboutLinkInfo(withSize: Constants.AboutLayout.DescriptionFontSize) linkInfoNode.isUserInteractionEnabled = true linkInfoNode.delegate = self @@ -45,6 +49,7 @@ class AboutNode: ASDisplayNode, ASTextNodeDelegate { scrollNode.addSubnode(sloganNode) scrollNode.addSubnode(descriptionNode) scrollNode.addSubnode(authorNode) + scrollNode.addSubnode(reviewNode) scrollNode.addSubnode(linkInfoNode) scrollNode.addSubnode(versionNode) @@ -57,6 +62,7 @@ class AboutNode: ASDisplayNode, ASTextNodeDelegate { scrollNode.automaticallyManagesSubnodes = true scrollNode.automaticallyManagesContentSize = true scrollNode.scrollableDirections = .down + scrollNode.view.showsVerticalScrollIndicator = false scrollNode.layoutSpecBlock = { node, constrainedSize in self.titleNode.style.alignSelf = .center self.sloganNode.style.alignSelf = .center @@ -66,13 +72,15 @@ class AboutNode: ASDisplayNode, ASTextNodeDelegate { self.descriptionNode.style.alignSelf = .start self.authorNode.style.alignSelf = .start self.authorNode.style.spacingBefore = 32.0 - self.authorNode.style.spacingAfter = 32.0 + self.reviewNode.style.alignSelf = .start + self.reviewNode.style.spacingBefore = 32.0 + self.reviewNode.style.spacingAfter = 32.0 self.linkInfoNode.style.alignSelf = .start self.linkInfoNode.style.spacingAfter = 32.0 self.versionNode.style.alignSelf = .center self.versionNode.style.spacingAfter = 8.0 let stack = ASStackLayoutSpec.vertical() - stack.children = [self.titleNode, self.sloganNode, self.descriptionNode, self.authorNode, self.linkInfoNode, self.versionNode] + stack.children = [self.titleNode, self.sloganNode, self.descriptionNode, self.authorNode, self.reviewNode, self.linkInfoNode, self.versionNode] return stack } @@ -105,6 +113,20 @@ class AboutNode: ASDisplayNode, ASTextNodeDelegate { return NSAttributedString.init(string: self.aboutModel.description, attributes: attr) } + private func attrStringForAboutReview(withSize size: CGFloat) -> NSAttributedString { + let attr = [ + NSForegroundColorAttributeName: UIColor.black, + NSFontAttributeName: UIFont.systemFont(ofSize: size) + ] + let attributedString = NSMutableAttributedString.init(string: self.aboutModel.review) + attributedString.addAttributes(attr, range: NSRange.init(location: 0, length: (self.aboutModel.review as NSString).length)) + attributedString.addAttributes([NSLinkAttributeName: Constants.AppStore.ReviewURL, + NSForegroundColorAttributeName: UIColor.blue, + NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue, NSUnderlineColorAttributeName: UIColor.clear], + range: (self.aboutModel.review as NSString).range(of: Constants.AppStore.TitleText)) + return attributedString + } + private func attrStringForAboutAuthor(withSize size: CGFloat) -> NSAttributedString { let attr = [ NSForegroundColorAttributeName: UIColor.black, @@ -170,6 +192,12 @@ class AboutNode: ASDisplayNode, ASTextNodeDelegate { func textNode(_ textNode: ASTextNode, tappedLinkAttribute attribute: String, value: Any, at point: CGPoint, textRange: NSRange) { guard let url = value as? URL else { return } - UIApplication.shared.openURL(url) + if UIApplication.shared.canOpenURL(url) { + if #available(iOS 10.0, *) { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } else { + UIApplication.shared.openURL(url) + } + } } } diff --git a/remotex-iOSTests/View/AboutNodeTests.swift b/remotex-iOSTests/View/AboutNodeTests.swift index 32d8a88..4bd2030 100644 --- a/remotex-iOSTests/View/AboutNodeTests.swift +++ b/remotex-iOSTests/View/AboutNodeTests.swift @@ -25,13 +25,14 @@ class AboutNodeTests: FBSnapshotTestCase { XCTAssert(aboutNode.subnodes[0].isKind(of: ASScrollNode.self)) let scrollNode = aboutNode.subnodes[0] - XCTAssertEqual(scrollNode.subnodes.count, 6) + XCTAssertEqual(scrollNode.subnodes.count, 7) let titleNode = scrollNode.subnodes[0] as! ASTextNode let sloganNode = scrollNode.subnodes[1] as! ASTextNode let descriptionNode = scrollNode.subnodes[2] as! ASTextNode let authorNode = scrollNode.subnodes[3] as! ASTextNode - let linkInfoNode = scrollNode.subnodes[4] as! ASTextNode - let versionNode = scrollNode.subnodes[5] as! ASTextNode + let reviewNode = scrollNode.subnodes[4] as! ASTextNode + let linkInfoNode = scrollNode.subnodes[5] as! ASTextNode + let versionNode = scrollNode.subnodes[6] as! ASTextNode XCTAssertNotNil(titleNode) XCTAssertEqual(titleNode.view.frame, CGRect.init(x: 0, y: 0, width: 0, height: 0)) @@ -58,6 +59,11 @@ class AboutNodeTests: FBSnapshotTestCase { let authorText = "RemoteX iOS App 由 @ArchimboldiMao 创建,并带领 RemoteX iOS 社区共同开发,详细贡献者信息请点击查看贡献者列表。\n欢迎前往社区 GitHub 主页围观源代码。" XCTAssertEqual(authorNode.attributedText?.string, authorText) + XCTAssertNotNil(reviewNode) + XCTAssertEqual(reviewNode.view.frame, CGRect.init(x: 0, y: 0, width: 0, height: 0)) + let reviewText = "喜欢“RemoteX”吗?\n请前往 App Store 为我们评分。" + XCTAssertEqual(reviewNode.attributedText?.string, reviewText) + XCTAssertNotNil(linkInfoNode) XCTAssertEqual(linkInfoNode.view.frame, CGRect.init(x: 0, y: 0, width: 0, height: 0)) let linkInfoText = "联系方式\n网 站:https://remotex.ooclab.org\nSlack:https://remotex.slack.com\n邮 件:admin@ooclab.org\nqq群: remotex 633498747\n微信群: 加 lijian_gnu 拉入群" diff --git a/remotex-iOSTests/View/ReferenceImages_64/remotex_iOSTests.AboutNodeTests/testInit@2x.png b/remotex-iOSTests/View/ReferenceImages_64/remotex_iOSTests.AboutNodeTests/testInit@2x.png index adb1f06..704336c 100644 Binary files a/remotex-iOSTests/View/ReferenceImages_64/remotex_iOSTests.AboutNodeTests/testInit@2x.png and b/remotex-iOSTests/View/ReferenceImages_64/remotex_iOSTests.AboutNodeTests/testInit@2x.png differ diff --git a/remotex-iOSTests/View/ReferenceImages_iOS_10/remotex_iOSTests.AboutNodeTests/testInit@2x.png b/remotex-iOSTests/View/ReferenceImages_iOS_10/remotex_iOSTests.AboutNodeTests/testInit@2x.png index adb1f06..704336c 100644 Binary files a/remotex-iOSTests/View/ReferenceImages_iOS_10/remotex_iOSTests.AboutNodeTests/testInit@2x.png and b/remotex-iOSTests/View/ReferenceImages_iOS_10/remotex_iOSTests.AboutNodeTests/testInit@2x.png differ