aboutsummaryrefslogtreecommitdiff
path: root/Hotline/macOS/NewsView.swift
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2023-12-19 20:38:13 -0800
committerDustin Mierau <dustin@mierau.me>2023-12-19 20:38:13 -0800
commit4fd69c02a3e7b581bb9229865336c315153f3b18 (patch)
treee6e11d872424196f2b4033077902126ad5556e40 /Hotline/macOS/NewsView.swift
parent5e87b5927cd931d46fb5f72fb035480a95969a9f (diff)
Beginnings of a UI for macOS as well as some visual changes to iOS client. :)
Diffstat (limited to 'Hotline/macOS/NewsView.swift')
-rw-r--r--Hotline/macOS/NewsView.swift248
1 files changed, 248 insertions, 0 deletions
diff --git a/Hotline/macOS/NewsView.swift b/Hotline/macOS/NewsView.swift
new file mode 100644
index 0000000..b5752d3
--- /dev/null
+++ b/Hotline/macOS/NewsView.swift
@@ -0,0 +1,248 @@
+import SwiftUI
+import UniformTypeIdentifiers
+
+struct NewsItemView: View {
+ @Environment(Hotline.self) private var model: Hotline
+
+ @State var expanded = false
+
+ var news: NewsInfo
+ let depth: Int
+
+ static var dateFormatter: DateFormatter = {
+ var dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = "MM/dd/yy, h:mm a"
+ dateFormatter.timeZone = .gmt
+ return dateFormatter
+ }()
+
+ var body: some View {
+ HStack {
+ if news.type == .bundle || news.type == .category {
+ Button {
+ news.expanded.toggle()
+ } label: {
+ Image(systemName: news.expanded ? "chevron.down" : "chevron.right")
+ .renderingMode(.template)
+ .frame(width: 10, height: 10)
+ .aspectRatio(contentMode: .fit)
+ .opacity(0.5)
+ }
+ .buttonStyle(.plain)
+ .frame(width: 12)
+ }
+ if news.type == .article {
+ HStack(alignment: .center) {
+ Image(systemName: "quote.opening")
+ }
+ .frame(width: 15)
+ }
+ Text(news.name)
+ .fontWeight((news.type == .bundle || news.type == .category) ? .bold : .regular)
+ .lineLimit(1)
+ .truncationMode(.tail)
+ if news.type == .article && news.articleUsername != nil {
+ Text(news.articleUsername!).foregroundStyle(.secondary).lineLimit(1)
+ }
+ if news.type == .bundle || news.type == .category {
+ Text("^[\(news.count) \(news.type == .bundle ? "Category" : "Post")](inflect: true)")
+
+// Text("\(news.count) \(news.type == .bundle ? "Categories" : "Posts")")
+ .foregroundStyle(.secondary)
+ .lineLimit(1)
+ .padding([.leading, .trailing], 8)
+ .padding([.top, .bottom], 2)
+ .background(Capsule(style: .circular).stroke(.secondary.opacity(0.3), lineWidth: 1))
+ }
+ Spacer()
+ if news.type == .article && news.articleUsername != nil {
+ if let d = news.articleDate {
+ Text(NewsItemView.dateFormatter.string(from: d)).foregroundStyle(.secondary)
+ }
+ }
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ .padding(.leading, CGFloat(depth * (12 + 10)))
+ .onChange(of: news.expanded) {
+// if news.isFolder {
+ print("EXPANDED \(news.name)? \(expanded)")
+ if news.type == .bundle || news.type == .category {
+ Task {
+ await model.getNewsList(at: news.path)
+// await model.getFileList(path: file.path)
+ }
+ }
+// }
+ }
+
+ if news.expanded {
+ ForEach(news.children, id: \.self) { childNews in
+ NewsItemView(news: childNews, depth: self.depth + 1).tag(childNews.id)
+ }
+ }
+ }
+
+// static let byteFormatter = ByteCountFormatter()
+//
+// private func formattedFileSize(_ fileSize: UInt) -> String {
+// // let bcf = ByteCountFormatter()
+// FileView.byteFormatter.allowedUnits = [.useAll]
+// FileView.byteFormatter.countStyle = .file
+// return FileView.byteFormatter.string(fromByteCount: Int64(fileSize))
+// }
+//
+// private func fileIcon(name: String) -> Image {
+// let fileExtension = (name as NSString).pathExtension
+// return Image(nsImage: NSWorkspace.shared.icon(for: UTType(filenameExtension: fileExtension) ?? UTType.content))
+// }
+}
+
+struct NewsView: View {
+ @Environment(Hotline.self) private var model: Hotline
+
+ @State private var selection: NewsInfo?
+ @State private var articleText: String?
+
+ var body: some View {
+ NavigationStack {
+ VSplitView {
+
+ // MARK: News Browser
+ List(model.news, id: \.self, selection: $selection) { newsItem in
+ NewsItemView(news: newsItem, depth: 0).tag(newsItem.id)
+ }
+
+ .frame(maxWidth: .infinity, minHeight: 100, idealHeight: 100)
+ .environment(\.defaultMinListRowHeight, 34)
+ .listStyle(.inset)
+ .alternatingRowBackgrounds(.enabled)
+ .task {
+ if !model.newsLoaded {
+ let _ = await model.getNewsList()
+ }
+ }
+ .contextMenu(forSelectionType: NewsInfo.self) { items in
+ // ...
+ } primaryAction: { items in
+ print("ITEMS?", items)
+ guard let clickedNews = items.first else {
+ return
+ }
+
+ self.selection = clickedNews
+ if clickedNews.type == .bundle || clickedNews.type == .category {
+ clickedNews.expanded.toggle()
+ }
+ }
+ .onChange(of: selection) {
+ if
+ let article = selection,
+ article.type == .article {
+ self.articleText = nil
+ if
+// let article = self.articleSelection.selectedArticle,
+ let articleFlavor = article.articleFlavors?.first,
+ let articleID = article.articleID {
+ Task {
+ if let articleText = await self.model.getNewsArticle(id: articleID, at: article.path, flavor: articleFlavor) {
+ self.articleText = articleText
+ }
+ }
+ }
+ }
+ }
+ .onKeyPress(.rightArrow) {
+ if let s = selection, s.type == .bundle || s.type == .category {
+ s.expanded = true
+ return .handled
+ }
+ return .ignored
+ }
+ .onKeyPress(.leftArrow) {
+ if let s = selection, s.type == .bundle || s.type == .category {
+ s.expanded = false
+ return .handled
+ }
+ return .ignored
+ }
+ .overlay {
+ if !model.newsLoaded {
+ VStack {
+ ProgressView()
+ .controlSize(.large)
+ }
+ .frame(maxWidth: .infinity)
+ }
+ }
+
+ // MARK: Article Viewer
+ ScrollView {
+ VStack(alignment: .leading, spacing: 0) {
+ if let news = selection {
+ if news.type == .article {
+
+ Text(news.name).font(.title)
+ .textSelection(.enabled)
+ .padding(.bottom, 8)
+
+ if let poster = news.articleUsername, let postDate = news.articleDate {
+ HStack(alignment: .firstTextBaseline) {
+ Text(poster)
+ .foregroundStyle(.secondary)
+ .lineLimit(1)
+ .truncationMode(.tail)
+ .textSelection(.enabled)
+ .padding(.bottom, 16)
+ Spacer()
+ Text("\(NewsItemView.dateFormatter.string(from: postDate))")
+ .foregroundStyle(.secondary)
+ .lineLimit(1)
+ .truncationMode(.tail)
+ .textSelection(.enabled)
+ .padding(.bottom, 16)
+ }
+ }
+
+ Divider()
+
+ if let newsText = self.articleText {
+ Text(newsText)
+ .textSelection(.enabled)
+ .lineSpacing(9)
+ .padding(.top, 16)
+ }
+ }
+ }
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .padding()
+ }
+ .frame(maxWidth: .infinity, minHeight: 200)
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ .background(Color(nsColor: .textBackgroundColor))
+ }
+ .toolbar {
+ ToolbarItem(placement:.primaryAction) {
+ Button {
+
+ } label: {
+ Image(systemName: "square.and.pencil")
+ }
+ }
+
+ ToolbarItem(placement:.primaryAction) {
+ Button {
+
+ } label: {
+ Image(systemName: "arrowshape.turn.up.left")
+ }
+ }
+ }
+ }
+}
+
+#Preview {
+ NewsView()
+ .environment(Hotline(trackerClient: HotlineTrackerClient(), client: HotlineClient()))
+}