diff --git a/HNReader.xcodeproj/project.pbxproj b/HNReader.xcodeproj/project.pbxproj index 17a366e..8d23334 100644 --- a/HNReader.xcodeproj/project.pbxproj +++ b/HNReader.xcodeproj/project.pbxproj @@ -23,9 +23,10 @@ C9B83DD926A863C000036AC6 /* Job.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B83DD826A863C000036AC6 /* Job.swift */; }; C9B83DDB26A863D000036AC6 /* Story.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B83DDA26A863D000036AC6 /* Story.swift */; }; C9B83DDF26A8808900036AC6 /* CommentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B83DDE26A8808900036AC6 /* CommentCell.swift */; }; - C9B83DE126A8820F00036AC6 /* HTMLText.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B83DE026A8820F00036AC6 /* HTMLText.swift */; }; C9B83DE926A8A23C00036AC6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C9B83DE826A8A23C00036AC6 /* Assets.xcassets */; }; C9B83DEB26A8A76800036AC6 /* +NSTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B83DEA26A8A76800036AC6 /* +NSTextField.swift */; }; + C9B83DED26A8ACD000036AC6 /* LoadingCircle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B83DEC26A8ACD000036AC6 /* LoadingCircle.swift */; }; + C9B83DEF26A8AF4A00036AC6 /* +String.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B83DEE26A8AF4A00036AC6 /* +String.swift */; }; C9D0937726741BBE002CC786 /* HNReaderApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D0937626741BBE002CC786 /* HNReaderApp.swift */; }; C9D0937926741BBE002CC786 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D0937826741BBE002CC786 /* HomeView.swift */; }; C9D0937E26741BBF002CC786 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C9D0937D26741BBF002CC786 /* Preview Assets.xcassets */; }; @@ -72,9 +73,10 @@ C9B83DD826A863C000036AC6 /* Job.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Job.swift; sourceTree = ""; }; C9B83DDA26A863D000036AC6 /* Story.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Story.swift; sourceTree = ""; }; C9B83DDE26A8808900036AC6 /* CommentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentCell.swift; sourceTree = ""; }; - C9B83DE026A8820F00036AC6 /* HTMLText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLText.swift; sourceTree = ""; }; C9B83DE826A8A23C00036AC6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; C9B83DEA26A8A76800036AC6 /* +NSTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "+NSTextField.swift"; sourceTree = ""; }; + C9B83DEC26A8ACD000036AC6 /* LoadingCircle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingCircle.swift; sourceTree = ""; }; + C9B83DEE26A8AF4A00036AC6 /* +String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "+String.swift"; sourceTree = ""; }; C9D0937326741BBE002CC786 /* HNReader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HNReader.app; sourceTree = BUILT_PRODUCTS_DIR; }; C9D0937626741BBE002CC786 /* HNReaderApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HNReaderApp.swift; sourceTree = ""; }; C9D0937826741BBE002CC786 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; @@ -126,6 +128,7 @@ children = ( 3307159309D438EFAA1259C7 /* +Date.swift */, C9B83DEA26A8A76800036AC6 /* +NSTextField.swift */, + C9B83DEE26A8AF4A00036AC6 /* +String.swift */, ); path = Utils; sourceTree = ""; @@ -160,7 +163,7 @@ children = ( C93F99B5267554F00046F870 /* StoryCell.swift */, C9B83DDE26A8808900036AC6 /* CommentCell.swift */, - C9B83DE026A8820F00036AC6 /* HTMLText.swift */, + C9B83DEC26A8ACD000036AC6 /* LoadingCircle.swift */, ); path = Components; sourceTree = ""; @@ -420,6 +423,7 @@ C9D0938326741BBF002CC786 /* HNReader.xcdatamodeld in Sources */, C9E9BD032674D095001B4E19 /* User.swift in Sources */, C9E9BCFF2674CB6C001B4E19 /* HackerNewsClient.swift in Sources */, + C9B83DEF26A8AF4A00036AC6 /* +String.swift in Sources */, C9E9BD012674D007001B4E19 /* HackerNews.swift in Sources */, C9D0937726741BBE002CC786 /* HNReaderApp.swift in Sources */, 330713D3016ED410AFD53FDF /* ItemListViewModel.swift in Sources */, @@ -427,11 +431,11 @@ 330711A9216E762026AF98A0 /* +Date.swift in Sources */, C9B58794267C153C005E0A50 /* DetailStoryView.swift in Sources */, C9B83DD426A8631300036AC6 /* Comment.swift in Sources */, - C9B83DE126A8820F00036AC6 /* HTMLText.swift in Sources */, 33071F1C64D4742E1F947FAA /* ItemDownloader.swift in Sources */, 3307147AB95F03650FC40B97 /* ItemCache.swift in Sources */, C9B83DD926A863C000036AC6 /* Job.swift in Sources */, C9B83DDF26A8808900036AC6 /* CommentCell.swift in Sources */, + C9B83DED26A8ACD000036AC6 /* LoadingCircle.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/HNReader/Utils/+String.swift b/HNReader/Utils/+String.swift new file mode 100644 index 0000000..ad5d6c4 --- /dev/null +++ b/HNReader/Utils/+String.swift @@ -0,0 +1,27 @@ +// +// +String.swift +// HNReader +// +// Created by Mattia Righetti on 21/07/21. +// + +import Foundation + +extension String { + init?(htmlEncodedString: String) { + guard let data = htmlEncodedString.data(using: .utf8) else { + return nil + } + + let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [ + .documentType: NSAttributedString.DocumentType.html, + .characterEncoding: String.Encoding.utf8.rawValue + ] + + guard let attributedString = try? NSAttributedString(data: data, options: options, documentAttributes: nil) else { + return nil + } + + self.init(attributedString.string) + } +} diff --git a/HNReader/View/Components/CommentCell.swift b/HNReader/View/Components/CommentCell.swift index f1737ad..6cc3efb 100644 --- a/HNReader/View/Components/CommentCell.swift +++ b/HNReader/View/Components/CommentCell.swift @@ -18,7 +18,7 @@ struct CommentCell: View { .foregroundColor(.yellow) HStack { - HTMLText(text: comment.text ?? "Empty comment") + Text(comment.text!) Spacer() } } diff --git a/HNReader/View/Components/HTMLText.swift b/HNReader/View/Components/HTMLText.swift deleted file mode 100644 index 81e34ad..0000000 --- a/HNReader/View/Components/HTMLText.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// HTMLText.swift -// HNReader -// -// Created by Mattia Righetti on 21/07/21. -// - -import SwiftUI - -struct HTMLText: NSViewRepresentable { - let text: String - - func updateNSView(_ nsView: NSViewType, context: Context) {} - - func makeNSView(context: Context) -> some NSView { - let label = NSTextField() - label.setHTMLFromString(htmlText: text) - label.backgroundColor = .clear - label.isBezeled = false - label.textColor = .white - label.isEditable = false - label.textColor = NSColor.init(cgColor: Color.white.opacity(0.7).cgColor!) - label.font = .systemFont(ofSize: 15) - label.sizeToFit() - return label - } -} - -struct HTMLText_Previews: PreviewProvider { - static var previews: some View { - HTMLText(text: "Bold") - } -} diff --git a/HNReader/View/Components/LoadingCircle.swift b/HNReader/View/Components/LoadingCircle.swift new file mode 100644 index 0000000..813c05f --- /dev/null +++ b/HNReader/View/Components/LoadingCircle.swift @@ -0,0 +1,41 @@ +// +// LoadingCircle.swift +// HNReader +// +// Created by Mattia Righetti on 21/07/21. +// + +import SwiftUI + +struct LoadingCircle: View { + @State private var isLoading = false + + var body: some View { + ZStack { + Circle() + .stroke(Color.gray.opacity(0.5), lineWidth: 10) + .frame(width: 50, height: 50) + + Circle() + .trim(from: 0, to: 0.2) + .stroke(Color.green.opacity(0.7), style: .init(lineWidth: 7, lineCap: .round)) + .frame(width: 50, height: 50) + .rotationEffect(Angle(degrees: isLoading ? 360 : 0)) + .animation( + Animation + .linear(duration: 1) + .repeatForever(autoreverses: false) + ) + .onAppear() { + self.isLoading = true + } + } + .frame(width: 70, height: 70) + } +} + +struct LoadingCircle_Previews: PreviewProvider { + static var previews: some View { + LoadingCircle() + } +} diff --git a/HNReader/View/DetailStoryView.swift b/HNReader/View/DetailStoryView.swift index 00366ec..48c69e0 100644 --- a/HNReader/View/DetailStoryView.swift +++ b/HNReader/View/DetailStoryView.swift @@ -60,7 +60,7 @@ struct DetailStoryView: View { @ViewBuilder func CommentsSection() -> some View { if fetching { - Text("Fetching") + LoadingCircle() } else { if let comments = comments { VStack(alignment: .leading) { @@ -92,6 +92,11 @@ struct DetailStoryView: View { DispatchQueue.main.async { self.comments = flattenComments.compactMap { $0 } as? [Comment] + if let comments = self.comments { + for comment in comments { + comment.text = String(htmlEncodedString: comment.text ?? "Empty comment") + } + } self.fetching = false } }