diff options
| author | Dustin Mierau <dustin@mierau.me> | 2023-12-19 20:38:13 -0800 |
|---|---|---|
| committer | Dustin Mierau <dustin@mierau.me> | 2023-12-19 20:38:13 -0800 |
| commit | 4fd69c02a3e7b581bb9229865336c315153f3b18 (patch) | |
| tree | e6e11d872424196f2b4033077902126ad5556e40 /Hotline/iOS/NewsView.swift | |
| parent | 5e87b5927cd931d46fb5f72fb035480a95969a9f (diff) | |
Beginnings of a UI for macOS as well as some visual changes to iOS client. :)
Diffstat (limited to 'Hotline/iOS/NewsView.swift')
| -rw-r--r-- | Hotline/iOS/NewsView.swift | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/Hotline/iOS/NewsView.swift b/Hotline/iOS/NewsView.swift new file mode 100644 index 0000000..d3e0bbd --- /dev/null +++ b/Hotline/iOS/NewsView.swift @@ -0,0 +1,235 @@ +import SwiftUI +import UniformTypeIdentifiers + +struct NewsItemView: View { + @Environment(Hotline.self) private var model: Hotline + @Environment(NewsItemSelection.self) private var selectedArticle: NewsItemSelection + + let news: NewsInfo + + static var dateFormatter: DateFormatter = { + var dateFormatter = DateFormatter() + dateFormatter.dateFormat = "MM/dd/yy, h:mm a" + dateFormatter.timeZone = .gmt + return dateFormatter + }() + + @State var expanded = false + + var body: some View { + if news.count > 0 { + DisclosureGroup(isExpanded: $expanded) { + ForEach(news.children) { childNews in + NewsItemView(news: childNews) + .frame(height: 38) + .environment(self.selectedArticle) + } + } label: { + HStack { + Text(news.name) + .fontWeight(.bold) + .lineLimit(1) + .truncationMode(.tail) + Spacer() + if news.count > 0 { + Text("\(news.count)") + .foregroundStyle(.secondary) + } + } + } + .onChange(of: expanded) { + if !expanded { + return + } + + Task { + await model.getNewsList(at: news.path) + } + } + } + else { + HStack { + Text(Image(systemName: "quote.opening")) + Text(news.name) + .lineLimit(1) + .truncationMode(.tail) + Spacer() + if news.count > 0 { + Text("\(news.count)") + .foregroundStyle(.secondary) + } + } + .onTapGesture { + if news.type == .article { + print("SELECTED", news.name) + selectedArticle.selectedArticle = news + } + } + } + } +} + +@Observable +class NewsItemSelection: Equatable { + var selectedArticle: NewsInfo? = nil + + static func == (lhs: NewsItemSelection, rhs: NewsItemSelection) -> Bool { + return lhs.selectedArticle == rhs.selectedArticle + } +} + +struct NewsView: View { + @Environment(Hotline.self) private var model: Hotline + @Environment(\.colorScheme) var colorScheme + + @State private var fetched = false + @State private var selectedCategory: NewsInfo? = nil + @State private var topListHeight: CGFloat = 200 + @State private var dividerHeight: CGFloat = 30 + + @State private var articleSelection = NewsItemSelection() + @State private var articleText = "" + +// @State private var selectedArticleID: UInt? + + var articleList: some View { + VStack(spacing: 0) { + if model.news.count == 0 { + Text("No News Available") + .font(.headline) + .opacity(0.3) + } + else { + List(model.news) { category in + NewsItemView(news: category) + .frame(height: 38) + .environment(self.articleSelection) + } + .scrollBounceBehavior(.basedOnSize) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color(uiColor: .systemGroupedBackground)) + + // .listStyle(.plain) + } + + var body: some View { + NavigationStack { + VStack(spacing: 0) { + articleList + .frame(height: topListHeight) + .frame(minHeight: topListHeight) + .onChange(of: self.articleSelection.selectedArticle) { + self.articleText = "" + 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 + } + } + } +// print("SELECTED ARTICLE", articleSelection.selectedArticle?.name) + } + + // Movable Divider + VStack(alignment: .center) { + Divider() + Spacer() + HStack(alignment: .center) { + Rectangle() + .fill(.tertiary) + .frame(width: 50, height: 6, alignment: .center) + .cornerRadius(10) + } + Spacer() + } + .background(colorScheme == .dark ? Color(white: 0.1) : Color(uiColor: UIColor.systemBackground)) + .frame(maxWidth: .infinity) + .frame(height: dividerHeight) + .gesture( + DragGesture() + .onChanged { gesture in + let delta = gesture.translation.height + topListHeight = max(min(topListHeight + delta, 500), 50) + } + ) + + // Reader View + ScrollView(.vertical) { + VStack(alignment: .leading) { + + if let news = self.articleSelection.selectedArticle { + if let poster = news.articleUsername, let postDate = news.articleDate { + HStack(alignment: .firstTextBaseline) { + Text(poster) + .foregroundStyle(.secondary) + .font(.subheadline) + .lineLimit(1) + .truncationMode(.tail) + .textSelection(.enabled) + .padding() + Spacer() + Text("\(NewsItemView.dateFormatter.string(from: postDate))") + .foregroundStyle(.secondary) + .font(.subheadline) + .lineLimit(1) + .textSelection(.enabled) + .padding() + } + .background(RoundedRectangle(cornerSize: CGSize(width: 8, height: 8)).fill(Color(uiColor: .tertiarySystemFill))) +// Capsule(style: .circular).fill(.secondary)) +// .padding(.bottom, 16) + .padding(.bottom, 8) + } + + Text(news.name).font(.title3) + .textSelection(.enabled) + + Divider() + } + + Text(self.articleText) + .multilineTextAlignment(.leading) + .padding(.top, 16) + } + .padding() + } + .scrollBounceBehavior(.basedOnSize) + .background(colorScheme == .dark ? Color(white: 0.1) : Color(uiColor: UIColor.systemBackground)) + } + .task { + if !fetched { + let _ = await model.getNewsList() + fetched = true + } + } + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .principal) { + Text(model.serverTitle) + .font(.headline) + } + ToolbarItem(placement: .navigationBarLeading) { + Button { + model.disconnect() + } label: { + Text(Image(systemName: "xmark.circle.fill")) + .symbolRenderingMode(.hierarchical) + .font(.title2) + .foregroundColor(.secondary) + } + } + } + } + } +} + +#Preview { + MessageBoardView() + .environment(HotlineState()) + .environment(Hotline(trackerClient: HotlineTrackerClient(), client: HotlineClient())) +} |