aboutsummaryrefslogtreecommitdiff
path: root/Hotline/iOS/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/iOS/NewsView.swift
parent5e87b5927cd931d46fb5f72fb035480a95969a9f (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.swift235
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()))
+}