diff --git a/Source/Hollow.xcodeproj/project.pbxproj b/Source/Hollow.xcodeproj/project.pbxproj index a51668bd..55a87a93 100644 --- a/Source/Hollow.xcodeproj/project.pbxproj +++ b/Source/Hollow.xcodeproj/project.pbxproj @@ -82,7 +82,6 @@ 0E42907B263A8584008DA046 /* AppCenterAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = 0E42907A263A8584008DA046 /* AppCenterAnalytics */; }; 0E42907D263A8584008DA046 /* AppCenterCrashes in Frameworks */ = {isa = PBXBuildFile; productRef = 0E42907C263A8584008DA046 /* AppCenterCrashes */; }; 0E42907F263A8584008DA046 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 0E42907E263A8584008DA046 /* Kingfisher */; }; - 0E429081263A8584008DA046 /* ImageScrollView in Frameworks */ = {isa = PBXBuildFile; productRef = 0E429080263A8584008DA046 /* ImageScrollView */; }; 0E429083263A8584008DA046 /* Cache in Frameworks */ = {isa = PBXBuildFile; productRef = 0E429082263A8584008DA046 /* Cache */; }; 0E4E4BC52634619A00B32F7F /* View+roundedCorner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4E4BC42634619A00B32F7F /* View+roundedCorner.swift */; }; 0E58810F25EA0F47006F6A94 /* View+presentPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E58810E25EA0F47006F6A94 /* View+presentPopover.swift */; }; @@ -313,10 +312,12 @@ 0ED1A16726346CB1001453A7 /* ReCAPTCHAPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED1A16626346CB1001453A7 /* ReCAPTCHAPageView.swift */; }; 0ED1A16826346CB1001453A7 /* ReCAPTCHAPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED1A16626346CB1001453A7 /* ReCAPTCHAPageView.swift */; }; 0ED1A16B26346D28001453A7 /* ViewLayouts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E415AF625CD772200351672 /* ViewLayouts.swift */; }; + 0ED1BF58264E664C005483C1 /* View+keyboardShortCut.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED1BF57264E664C005483C1 /* View+keyboardShortCut.swift */; }; 0ED81A25263D4C2300939B82 /* View+defaultToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED81A24263D4C2300939B82 /* View+defaultToggleStyle.swift */; }; 0ED82F0026103FB600A799C0 /* View+defaultPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED82EFF26103FB600A799C0 /* View+defaultPadding.swift */; }; 0EDA262725D22C8600B67292 /* DeviceListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDA262625D22C8600B67292 /* DeviceListView.swift */; }; 0EDA263125D2465100B67292 /* LoadingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDA263025D2465100B67292 /* LoadingIndicator.swift */; }; + 0EDEC0E52650E5A6003112BA /* View+makeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDEC0E42650E5A6003112BA /* View+makeButton.swift */; }; 0EE60EFD25FCCCD700D97E7A /* CheckmarkButtonImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE60EFC25FCCCD700D97E7A /* CheckmarkButtonImage.swift */; }; 0EF10F6626330F29004D6538 /* HollowDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF10F6526330F29004D6538 /* HollowDetailViewController.swift */; }; 0EFA3DDE25D3A03D0003C76B /* LoadingLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFA3DDD25D3A03D0003C76B /* LoadingLabel.swift */; }; @@ -551,10 +552,12 @@ 0ED087D026354556003C2E11 /* PostListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListView.swift; sourceTree = ""; }; 0ED1A15F26346C37001453A7 /* ReCAPTCHAWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReCAPTCHAWebView.swift; sourceTree = ""; }; 0ED1A16626346CB1001453A7 /* ReCAPTCHAPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReCAPTCHAPageView.swift; sourceTree = ""; }; + 0ED1BF57264E664C005483C1 /* View+keyboardShortCut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+keyboardShortCut.swift"; sourceTree = ""; }; 0ED81A24263D4C2300939B82 /* View+defaultToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+defaultToggleStyle.swift"; sourceTree = ""; }; 0ED82EFF26103FB600A799C0 /* View+defaultPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+defaultPadding.swift"; sourceTree = ""; }; 0EDA262625D22C8600B67292 /* DeviceListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceListView.swift; sourceTree = ""; }; 0EDA263025D2465100B67292 /* LoadingIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingIndicator.swift; sourceTree = ""; }; + 0EDEC0E42650E5A6003112BA /* View+makeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+makeButton.swift"; sourceTree = ""; }; 0EE60EFC25FCCCD700D97E7A /* CheckmarkButtonImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckmarkButtonImage.swift; sourceTree = ""; }; 0EF10F6526330F29004D6538 /* HollowDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HollowDetailViewController.swift; sourceTree = ""; }; 0EFA3DDD25D3A03D0003C76B /* LoadingLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingLabel.swift; sourceTree = ""; }; @@ -597,7 +600,6 @@ 0E429079263A8584008DA046 /* Alamofire in Frameworks */, 0E429077263A8584008DA046 /* Defaults in Frameworks */, 0E429075263A8584008DA046 /* WaterfallGrid in Frameworks */, - 0E429081263A8584008DA046 /* ImageScrollView in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -853,6 +855,8 @@ 0E36F54525D0F0FB001AE852 /* GetFrame.swift */, 0E6721D525DE2DDB00A84311 /* GetSafeAreaInsets.swift */, 0E186C38262C60A1001A5855 /* View+showToast.swift */, + 0ED1BF57264E664C005483C1 /* View+keyboardShortCut.swift */, + 0EDEC0E42650E5A6003112BA /* View+makeButton.swift */, ); path = Utilities; sourceTree = ""; @@ -1315,7 +1319,6 @@ 0E42907A263A8584008DA046 /* AppCenterAnalytics */, 0E42907C263A8584008DA046 /* AppCenterCrashes */, 0E42907E263A8584008DA046 /* Kingfisher */, - 0E429080263A8584008DA046 /* ImageScrollView */, 0E429082263A8584008DA046 /* Cache */, ); productName = HollowMac; @@ -1441,6 +1444,7 @@ 0E415B5425CD772200351672 /* ImageButtonModifier.swift in Sources */, 0E6721DD25DE326400A84311 /* View+conditionalPadding.swift in Sources */, 0EFE13F4262876FE007B0045 /* View+imageSaver.swift in Sources */, + 0EDEC0E52650E5A6003112BA /* View+makeButton.swift in Sources */, 0EC2458126340311001AFC4B /* SystemMessage.swift in Sources */, 0E3E6960260E3676005E8DA2 /* SplitView.swift in Sources */, 0E6721D625DE2DDB00A84311 /* GetSafeAreaInsets.swift in Sources */, @@ -1468,6 +1472,7 @@ 0EC245FF26340312001AFC4B /* PostCache.swift in Sources */, 0E1DD7D625D983BE000D27BA /* Avatar.swift in Sources */, 0EC245D326340312001AFC4B /* Request+publisher.swift in Sources */, + 0ED1BF58264E664C005483C1 /* View+keyboardShortCut.swift in Sources */, 0EC2458D26340311001AFC4B /* HollowImage.swift in Sources */, 0EC2462E2634040D001AFC4B /* LoginStore.swift in Sources */, 0EC245A526340311001AFC4B /* DefaultRequest.swift in Sources */, @@ -1856,7 +1861,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Hollow/Hollow.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 47; + CURRENT_PROJECT_VERSION = 48; DEVELOPMENT_TEAM = C5UH93T368; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Hollow/Info.plist; @@ -1865,7 +1870,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.0.9; + MARKETING_VERSION = 3.0.10; PRODUCT_BUNDLE_IDENTIFIER = treehollow.Hollow; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1882,7 +1887,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Hollow/Hollow.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 47; + CURRENT_PROJECT_VERSION = 48; DEVELOPMENT_TEAM = C5UH93T368; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Hollow/Info.plist; @@ -1891,7 +1896,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.0.9; + MARKETING_VERSION = 3.0.10; PRODUCT_BUNDLE_IDENTIFIER = treehollow.Hollow; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -2127,11 +2132,6 @@ package = 0E429068263A8524008DA046 /* XCRemoteSwiftPackageReference "Kingfisher" */; productName = Kingfisher; }; - 0E429080263A8584008DA046 /* ImageScrollView */ = { - isa = XCSwiftPackageProductDependency; - package = 0E42906E263A8557008DA046 /* XCRemoteSwiftPackageReference "ImageScrollView" */; - productName = ImageScrollView; - }; 0E429082263A8584008DA046 /* Cache */ = { isa = XCSwiftPackageProductDependency; package = 0E429071263A8572008DA046 /* XCRemoteSwiftPackageReference "Cache" */; diff --git a/Source/Hollow/Assets.xcassets/Universal/hollow.content.comment.quote.text.colorset/Contents.json b/Source/Hollow/Assets.xcassets/Universal/hollow.content.comment.quote.text.colorset/Contents.json new file mode 100644 index 00000000..a1a31914 --- /dev/null +++ b/Source/Hollow/Assets.xcassets/Universal/hollow.content.comment.quote.text.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0xB2", + "green" : "0xA5", + "red" : "0xA6" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0x8D", + "green" : "0x7B", + "red" : "0x7C" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Source/Hollow/View/Customization/CustomColors.swift b/Source/Hollow/View/Customization/CustomColors.swift index 8d2c8e07..181b866a 100644 --- a/Source/Hollow/View/Customization/CustomColors.swift +++ b/Source/Hollow/View/Customization/CustomColors.swift @@ -48,4 +48,5 @@ extension Color { static var tint: Color { customColor(prefix: "tint") } + static let hollowCommentQuoteText = Color("hollow.content.comment.quote.text") } diff --git a/Source/Hollow/View/Hierarchy/Hollow/Content/HollowCommentContentView.swift b/Source/Hollow/View/Hierarchy/Hollow/Content/HollowCommentContentView.swift index a59a515d..5f8c3f3a 100644 --- a/Source/Hollow/View/Hierarchy/Hollow/Content/HollowCommentContentView.swift +++ b/Source/Hollow/View/Hierarchy/Hollow/Content/HollowCommentContentView.swift @@ -17,6 +17,7 @@ struct HollowCommentContentView: View { var postColorIndex: Int var postHash: Int var imageReloadHandler: ((HollowImage) -> Void)? = nil + var jumpToReplyingHandler: (() -> Void)? = nil private let compactLineLimit = 3 private var nameLabelWidth: CGFloat { return compact ? labelWidth : body45 @@ -85,6 +86,31 @@ struct HollowCommentContentView: View { } } + if let commentInfo = commentData.replyToCommentInfo, !compact { + let nameText = Text(verbatim: "\(commentInfo.name) ").bold() + Group { + if commentInfo.text != "" { + nameText + Text(commentInfo.text) + } else if commentInfo.hasImage { + nameText + Text(Image(systemName: "photo")) + } else { + nameText + } + } + .leading() + .dynamicFont(size: 14) + .lineLimit(3) + .lineSpacing(0.8) + .padding(4) + .padding(.horizontal, 2) + .foregroundColor(.hollowCommentQuoteText) + .background(Color.hollowCommentQuoteText.opacity(0.09)) + .cornerRadius(3) + .padding(.vertical, 2) + .makeButton(action: { jumpToReplyingHandler?() }) + .buttonStyle(PlainButtonStyle()) + } + if commentData.image != nil && !compact { HollowImageView( hollowImage: commentData.image, @@ -94,34 +120,33 @@ struct HollowCommentContentView: View { ) .roundedCorner(4) .padding(.top, UIDevice.isMac ? 10 : 5) - .padding(.bottom, UIDevice.isMac ? 13 : 10) .fixedSize(horizontal: false, vertical: true) } - Group { - let replyText = (commentData.replyTo != -1 ? - Text(Image(systemName: "arrow.turn.up.right")) + Text(" ") : - Text("") - ) - .foregroundColor(Color.hollowCardStarUnselected.opacity(colorScheme == .light ? 0.3 : 0.8)) - - if commentData.text != "" { - - if compact || !commentData.renderHighlight { - replyText + Text(commentData.text) - } else { - replyText + Text.highlightLinksAndCitation(commentData.text, modifiers: { - $0.underline() - .foregroundColor(.hollowContentText) - }) + + if commentData.text != "" || (commentData.image != nil && compact) { + if commentData.image != nil && !compact { + Spacer(minLength: UIDevice.isMac ? 10 : 5).fixedSize() + } + Group { + if commentData.text != "" { + if compact || !commentData.renderHighlight { + Text(commentData.text) + } else { + Text.highlightLinksAndCitation(commentData.text, modifiers: { + $0.underline() + .foregroundColor(.hollowContentText) + }) + } + } else if commentData.image != nil && compact { + (Text(verbatim: "[") + Text("TEXTVIEW_PHOTO_PLACEHOLDER_TEXT") + Text(verbatim: "]")) + .foregroundColor(.uiColor(.secondaryLabel)) } - } else if commentData.image != nil && compact { - (replyText + Text("[") + Text("TEXTVIEW_PHOTO_PLACEHOLDER_TEXT") + Text("]")) - .foregroundColor(.uiColor(.secondaryLabel)) } + .leading() + .lineLimit(compact ? compactLineLimit : nil) + .layoutPriority(1) } - .leading() - .lineLimit(compact ? compactLineLimit : nil) - .layoutPriority(1) + } } diff --git a/Source/Hollow/View/Hierarchy/Hollow/Detail/HollowDetailSubViews.swift b/Source/Hollow/View/Hierarchy/Hollow/Detail/HollowDetailSubViews.swift index db3e8370..e5481474 100644 --- a/Source/Hollow/View/Hierarchy/Hollow/Detail/HollowDetailSubViews.swift +++ b/Source/Hollow/View/Hierarchy/Hollow/Detail/HollowDetailSubViews.swift @@ -44,7 +44,7 @@ extension HollowDetailView { Button(action: { withAnimation { reverseComments.toggle() }}) { HStack(spacing: 5) { Text(reverseComments ? "HOLLOWDETAIL_COMMENTS_ORDER_NEW_TO_OLD" : "HOLLOWDETAIL_COMMENTS_ORDER_OLD_TO_NEW") - Image(systemName: "arrowtriangle.up") + Image(systemName: "arrow.up") .rotationEffect(Angle(degrees: reverseComments ? 180 : 0)) } .dynamicFont(size: 15, weight: .medium) @@ -68,7 +68,7 @@ extension HollowDetailView { .padding(.bottom, postData.comments.isEmpty ? 15 : 0) } } - + // To hide the last seperator Color.hollowCardBackground .frame(height: commentViewBottomPadding) @@ -97,87 +97,93 @@ extension HollowDetailView { } ) - let highlighted = store.replyToIndex == index || jumpedToIndex == index + let highlighted = store.replyToIndex == index || jumpedToCommentId == comment.commentId - HollowCommentContentView(commentData: bindingComment, compact: false, contentVerticalPadding: UIDevice.isMac ? 13 : 10, hideLabel: hideLabel, postColorIndex: store.postDataWrapper.post.colorIndex, postHash: store.postDataWrapper.post.hash, imageReloadHandler: { store.reloadImage($0, commentId: comment.commentId) }) - .id(index) - .padding(.horizontal) - .background( - Group { - store.replyToIndex == index || jumpedToIndex == index ? - Color.background : Color.hollowCardBackground - } - .roundedCorner(highlighted ? 10 : 0) - .padding(.horizontal, highlighted ? 10 : 0) - .transition(.opacity) - ) - .contentShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) - .onClickGesture { - guard !store.isSendingComment && !store.isLoading else { return } - UIImpactFeedbackGenerator(style: .soft).impactOccurred() - withAnimation(scrollAnimation) { - store.replyToIndex = index - jumpedToIndex = nil + HollowCommentContentView( + commentData: bindingComment, + compact: false, + contentVerticalPadding: UIDevice.isMac ? 13 : 10, + hideLabel: hideLabel, + postColorIndex: store.postDataWrapper.post.colorIndex, + postHash: store.postDataWrapper.post.hash, + imageReloadHandler: { store.reloadImage($0, commentId: comment.commentId) }, + jumpToReplyingHandler: { jumpToComment(commentId: comment.replyTo) } + ) + .padding(.horizontal) + .background( + Group { + highlighted ? Color.background : Color.hollowCardBackground + } + .roundedCorner(highlighted ? 10 : 0) + .padding(.horizontal, highlighted ? 10 : 0) + .transition(.opacity) + ) + .contentShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) + .onClickGesture { + guard !store.isSendingComment && !store.isLoading else { return } + UIImpactFeedbackGenerator(style: .soft).impactOccurred() + withAnimation(scrollAnimation) { + store.replyToIndex = index + jumpedToCommentId = nil + } + } + .contextMenu { + if comment.text != "" { + Button(action: { + UIPasteboard.general.string = comment.text + }, label: { + Label(NSLocalizedString("COMMENT_VIEW_COPY_TEXT_LABEL", comment: ""), systemImage: "doc.on.doc") + }) + } + + if showOnlyName == nil { + Button(action: { withAnimation { showOnlyName = comment.name } }) { + Label("COMMENT_VIEW_SHOW_ONLY_LABEL", systemImage: "line.horizontal.3.decrease.circle") } + Divider() } - .contextMenu { - if comment.text != "" { + + if comment.hasURL { + let links = Array(comment.text.links().compactMap({ URL(string: $0) })) + Divider() + ForEach(links, id: \.self) { link in Button(action: { - UIPasteboard.general.string = comment.text - }, label: { - Label(NSLocalizedString("COMMENT_VIEW_COPY_TEXT_LABEL", comment: ""), systemImage: "doc.on.doc") - }) - Divider() - } - - if showOnlyName == nil { - Button(action: { withAnimation { showOnlyName = comment.name } }) { - Label("COMMENT_VIEW_SHOW_ONLY_LABEL", systemImage: "person.crop.circle.badge.checkmark") + let helper = OpenURLHelper(openURL: openURL) + try? helper.tryOpen(link, method: Defaults[.openURLMethod]) + }) { + Label(link.absoluteString, systemImage: "link") } } - - // FIXME: Remove !useListInDetail - if comment.replyTo != -1 && !useListInDetail { + Divider() + } + if comment.hasCitedNumbers { + let citedPosts = comment.text.citationNumbers() + Divider() + ForEach(citedPosts, id: \.self) { post in + let wrapper = PostDataWrapper.templatePost(for: post) Button(action: { - let comments = store.postDataWrapper.post.comments - guard let index = comments.firstIndex(where: {$0.commentId == comment.replyTo}) else { return } - jumpedIndexFromComment = index + IntegrationUtilities.conditionallyPresentDetail(store: .init(bindingPostWrapper: .constant(wrapper))) }) { - Label("COMMENT_VIEW_JUMP_LABEL", systemImage: "text.insert") - } - } - if comment.hasURL { - let links = Array(comment.text.links().compactMap({ URL(string: $0) })) - Divider() - ForEach(links, id: \.self) { link in - Button(action: { - let helper = OpenURLHelper(openURL: openURL) - try? helper.tryOpen(link, method: Defaults[.openURLMethod]) - }) { - Label(link.absoluteString, systemImage: "link") - } + Label("#\(post.string)", systemImage: "text.quote") } - Divider() } - if comment.hasCitedNumbers { - let citedPosts = comment.text.citationNumbers() - ForEach(citedPosts, id: \.self) { post in - let wrapper = PostDataWrapper.templatePost(for: post) - Button(action: { - IntegrationUtilities.conditionallyPresentDetail(store: .init(bindingPostWrapper: .constant(wrapper))) - }) { - Label("#\(post.string)", systemImage: "text.quote") - } - } - Divider() - } - ReportMenuContent( - store: store, - permissions: comment.permissions, - commentId: comment.commentId - ) + Divider() } + ReportMenuContent( + store: store, + permissions: comment.permissions, + commentId: comment.commentId + ) + } + .id(comment.commentId) + }} + + } + + func jumpToComment(commentId: Int) { + withAnimation(scrollAnimation) { store.replyToIndex = -2 } + jumpedFromCommentId = commentId } struct PlaceholderComment: View { diff --git a/Source/Hollow/View/Hierarchy/Hollow/Detail/HollowDetailView.swift b/Source/Hollow/View/Hierarchy/Hollow/Detail/HollowDetailView.swift index 0b1dccc5..2ffe5f23 100644 --- a/Source/Hollow/View/Hierarchy/Hollow/Detail/HollowDetailView.swift +++ b/Source/Hollow/View/Hierarchy/Hollow/Detail/HollowDetailView.swift @@ -15,8 +15,8 @@ struct HollowDetailView: View { @State private var headerFrame: CGRect = .zero @State private var commentViewFrame: CGRect = .zero @State var inputPresented = false - @State var jumpedToIndex: Int? - @State var jumpedIndexFromComment: Int? + @State var jumpedToCommentId: Int? + @State var jumpedFromCommentId: Int? @State private var showHeaderContent = Defaults[.useListInDetail] @State var reverseComments = false @State var showOnlyName: String? @@ -106,19 +106,22 @@ struct HollowDetailView: View { .edgesIgnoringSafeArea(.bottom) .onChange(of: store.replyToIndex) { index in + guard index != -2 else { return } + let id = index >= 0 ? store.postDataWrapper.post.comments[index].commentId : -1 withAnimation(scrollAnimation) { - proxy.scrollTo(index, anchor: scrollToAnchor) + proxy.scrollTo(id, anchor: scrollToAnchor) } } - .onChange(of: jumpedIndexFromComment) { index in - if index != nil { + .onChange(of: jumpedFromCommentId) { commentId in + if commentId != nil { withAnimation(scrollAnimation) { - jumpedIndexFromComment = nil - proxy.scrollTo(index, anchor: scrollToAnchor) - jumpedToIndex = index + jumpedFromCommentId = nil + proxy.scrollTo(commentId, anchor: scrollToAnchor) + jumpedToCommentId = commentId } DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - withAnimation(scrollAnimation) { jumpedToIndex = nil } + guard self.jumpedToCommentId == commentId else { return } + withAnimation(scrollAnimation) { jumpedToCommentId = nil } } } } @@ -128,15 +131,15 @@ struct HollowDetailView: View { // Check if there is any comment to jump to // when finish loading if let jumpToCommentId = store.jumpToCommentId { - if let index = store.postDataWrapper.post.comments.firstIndex(where: { $0.commentId == jumpToCommentId }) { withAnimation(scrollAnimation) { - proxy.scrollTo(index, anchor: scrollToAnchor) - jumpedToIndex = index + proxy.scrollTo(jumpToCommentId, anchor: scrollToAnchor) + jumpedToCommentId = jumpToCommentId } DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - withAnimation(scrollAnimation) { jumpedToIndex = nil } + guard self.jumpedToCommentId == jumpToCommentId else { return } + withAnimation(scrollAnimation) { + self.jumpedToCommentId = nil } } - } store.jumpToCommentId = nil } } @@ -229,6 +232,7 @@ extension HollowDetailView { Spacer(minLength: 5).fixedSize() .background(Color.hollowCardBackground) + .id(-1) if store.noSuchPost { Text("DETAILVIEW_NO_SUCH_POST_PLACEHOLDER") @@ -245,7 +249,6 @@ extension HollowDetailView { ) .padding(.top, spacing) .padding(.horizontal) - .id(-1) } Spacer(minLength: spacing * 2).fixedSize() diff --git a/Source/Hollow/View/Hierarchy/Main/SearchSubViews.swift b/Source/Hollow/View/Hierarchy/Main/SearchSubViews.swift index c2331d58..2a7a1c3c 100644 --- a/Source/Hollow/View/Hierarchy/Main/SearchSubViews.swift +++ b/Source/Hollow/View/Hierarchy/Main/SearchSubViews.swift @@ -69,6 +69,7 @@ extension SearchView { } .disabled(store.isLoading || !searchStringValid) .opacity(presented ? 1 : 0) + .keyboardShortcut("r") } var closeButton: some View { diff --git a/Source/Hollow/View/Hierarchy/iPad/HollowDetailView_iPad.swift b/Source/Hollow/View/Hierarchy/iPad/HollowDetailView_iPad.swift index 66e165e9..a43a8308 100644 --- a/Source/Hollow/View/Hierarchy/iPad/HollowDetailView_iPad.swift +++ b/Source/Hollow/View/Hierarchy/iPad/HollowDetailView_iPad.swift @@ -64,6 +64,7 @@ struct HollowDetailView_iPad: View { .padding(.vertical, 5) .foregroundColor(attention ? .hollowCardStarSelected : .hollowCardStarUnselected) } + .keyboardShortcut("d") .disabled(store.isEditingAttention || store.noSuchPost) } @@ -105,6 +106,6 @@ struct HollowDetailView_iPad: View { } .padding(.leading) ) - + .keyboardShortcut("r", action: store.requestDetail) } } diff --git a/Source/Hollow/View/Hierarchy/iPad/MainSubViews_iPad.swift b/Source/Hollow/View/Hierarchy/iPad/MainSubViews_iPad.swift index 26e0d44a..009ec080 100644 --- a/Source/Hollow/View/Hierarchy/iPad/MainSubViews_iPad.swift +++ b/Source/Hollow/View/Hierarchy/iPad/MainSubViews_iPad.swift @@ -88,6 +88,7 @@ extension MainView_iPad { }, systemImageName: "plus" ) + .keyboardShortcut("n") .bottom() .trailing() .padding() diff --git a/Source/Hollow/View/Integration/ImageViewer.swift b/Source/Hollow/View/Integration/ImageViewer.swift index 738d0364..7ba04ebb 100644 --- a/Source/Hollow/View/Integration/ImageViewer.swift +++ b/Source/Hollow/View/Integration/ImageViewer.swift @@ -137,7 +137,7 @@ struct ImageScrollViewWrapper: UIViewRepresentable { let view = ImageScrollView() view.setup() view.display(image: image) - view.maxScaleFromMinScale = 4 + view.maxScaleFromMinScale = 1.5 * max(2.5, image.size.height / image.size.width) view.imageScrollViewDelegate = context.coordinator view.imageContentMode = .aspectFit view.alwaysBounceVertical = true diff --git a/Source/Hollow/View/Modifiers/View+refreshNavigationItem.swift b/Source/Hollow/View/Modifiers/View+refreshNavigationItem.swift index 4d046941..9ef8d1cc 100644 --- a/Source/Hollow/View/Modifiers/View+refreshNavigationItem.swift +++ b/Source/Hollow/View/Modifiers/View+refreshNavigationItem.swift @@ -16,6 +16,7 @@ extension View { Button(action: refreshAction) { Image(systemName: "arrow.clockwise") } + .keyboardShortcut("r") #endif }) } diff --git a/Source/Hollow/View/Utilities/View+keyboardShortCut.swift b/Source/Hollow/View/Utilities/View+keyboardShortCut.swift new file mode 100644 index 00000000..c81fc261 --- /dev/null +++ b/Source/Hollow/View/Utilities/View+keyboardShortCut.swift @@ -0,0 +1,16 @@ +// +// View+keyboardShortCut.swift +// Hollow +// +// Created by liang2kl on 2021/5/14. +// Copyright © 2021 treehollow. All rights reserved. +// + +import SwiftUI + +extension View { + func keyboardShortcut(_ key: KeyEquivalent, action: @escaping () -> Void) -> some View { + return self + .overlay(Button("", action: action).keyboardShortcut(key).opacity(0)) + } +} diff --git a/Source/Hollow/View/Utilities/View+makeButton.swift b/Source/Hollow/View/Utilities/View+makeButton.swift new file mode 100644 index 00000000..70b6ce4a --- /dev/null +++ b/Source/Hollow/View/Utilities/View+makeButton.swift @@ -0,0 +1,15 @@ +// +// View+makeButton.swift +// Hollow +// +// Created by liang2kl on 2021/5/16. +// Copyright © 2021 treehollow. All rights reserved. +// + +import SwiftUI + +extension View { + func makeButton(action: @escaping () -> Void) -> some View { + Button(action: action, label: { self }) + } +} diff --git a/Source/Shared/CrossPlatform/CrossPlatformTypes.swift b/Source/Shared/CrossPlatform/CrossPlatformTypes.swift index c4bfef19..572cd94f 100644 --- a/Source/Shared/CrossPlatform/CrossPlatformTypes.swift +++ b/Source/Shared/CrossPlatform/CrossPlatformTypes.swift @@ -11,8 +11,15 @@ import SwiftUI typealias HImage = NSImage typealias HView = NSView typealias HColor = NSColor +struct UIDevice { + static let isMac = true + static let isGenericMac = true +} #else typealias HImage = UIImage typealias HColor = UIColor typealias HView = UIView +extension UIDevice { + static let isGenericMac = false +} #endif diff --git a/Source/Shared/Model/Net/Other/UpdateAvailabilityRequest.swift b/Source/Shared/Model/Net/Other/UpdateAvailabilityRequest.swift index 9403b660..6b76bcef 100644 --- a/Source/Shared/Model/Net/Other/UpdateAvailabilityRequest.swift +++ b/Source/Shared/Model/Net/Other/UpdateAvailabilityRequest.swift @@ -10,7 +10,9 @@ import Foundation import Defaults import Alamofire import Combine +#if canImport(UIKit) import UIKit +#endif struct UpdateAvailabilityRequestResult: Codable { struct Result: Codable { @@ -44,7 +46,7 @@ struct UpdateAvailabilityRequest: Request { } func performRequest(completion: @escaping (ResultData?, DefaultRequestError?) -> Void) { - if UIDevice.isMac { + if UIDevice.isMac && !UIDevice.isGenericMac { // FIXME: Seems that we cannot get macOS version info with the same bundle id. completion(nil, nil) return diff --git a/Source/Shared/Model/Types/Data/CommentData.swift b/Source/Shared/Model/Types/Data/CommentData.swift index e10cc15a..1e0adbcf 100644 --- a/Source/Shared/Model/Types/Data/CommentData.swift +++ b/Source/Shared/Model/Types/Data/CommentData.swift @@ -39,8 +39,19 @@ struct CommentData: Identifiable, Codable { var colorIndex: Int var abbreviation: String + // Additional replying to comment info + var replyToCommentInfo: ReplyToCommentInfo? + mutating func updateHashAndColor() { hash = AvatarGenerator.hash(postId: postId, name: name) colorIndex = AvatarGenerator.colorIndex(hash: hash) } } + +extension CommentData { + struct ReplyToCommentInfo: Codable { + var name: String + var text: String + var hasImage: Bool + } +} diff --git a/Source/Shared/Model/Types/Post.swift b/Source/Shared/Model/Types/Post.swift index e0167a77..f64c800c 100644 --- a/Source/Shared/Model/Types/Post.swift +++ b/Source/Shared/Model/Types/Post.swift @@ -67,6 +67,15 @@ extension Post { text.removeLast() } + // Process replying to comment info + var comments = comments + for index in comments.indices { + if comments[index].replyTo != -1, + let replyToComment = comments.first(where: { $0.commentId == comments[index].replyTo }) { + comments[index].replyToCommentInfo = .init(name: replyToComment.name, text: replyToComment.text, hasImage: replyToComment.image != nil) + } + } + let hash = AvatarGenerator.hash(postId: pid, name: "") return PostData( attention: attention, diff --git a/Source/Shared/Model/Utilities/VersionUpdateUtilities.swift b/Source/Shared/Model/Utilities/VersionUpdateUtilities.swift index e0b67ce2..62ebecdd 100644 --- a/Source/Shared/Model/Utilities/VersionUpdateUtilities.swift +++ b/Source/Shared/Model/Utilities/VersionUpdateUtilities.swift @@ -2,7 +2,7 @@ // VersionUpdateUtilities.swift // Hollow // -// Created by 梁业升 on 2021/4/27. +// Created by liang2kl on 2021/4/27. // Copyright © 2021 treehollow. All rights reserved. //