From eb4dc011b664caa98bef69d6337f194165e280be Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Fri, 16 Dec 2022 13:16:48 +0100 Subject: [PATCH] A bit better timeline UI --- .../Models/Sources/Models/Ext/StatusExt.swift | 25 +++++++- Packages/Models/Sources/Models/Status.swift | 17 ++++- .../Timeline/Status/StatusActionsView.swift | 35 +++++++++++ .../Timeline/Status/StatusRowView.swift | 63 +++++++++++++------ 4 files changed, 118 insertions(+), 22 deletions(-) create mode 100644 Packages/Timeline/Sources/Timeline/Status/StatusActionsView.swift diff --git a/Packages/Models/Sources/Models/Ext/StatusExt.swift b/Packages/Models/Sources/Models/Ext/StatusExt.swift index 59d20278..a79ef214 100644 --- a/Packages/Models/Sources/Models/Ext/StatusExt.swift +++ b/Packages/Models/Sources/Models/Ext/StatusExt.swift @@ -1,7 +1,7 @@ import HTML2Markdown import Foundation -extension Status { +extension AnyStatus { private static var createdAtDateFormatter: DateFormatter { let dateFormatter = DateFormatter() dateFormatter.calendar = .init(identifier: .iso8601) @@ -16,6 +16,12 @@ extension Status { return dateFormatter } + private static var createdAtShortDateFormatted: DateFormatter { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .medium + return dateFormatter + } + public var contentAsMarkdown: String { do { let dom = try HTMLParser().parse(html: content) @@ -30,6 +36,21 @@ extension Status { } public var createdAtFormatted: String { - Self.createdAtRelativeFormatter.localizedString(for: createdAtDate, relativeTo: Date()) + let calendar = Calendar(identifier: .gregorian) + if calendar.numberOfDaysBetween(createdAtDate, and: Date()) > 1 { + return Self.createdAtShortDateFormatted.string(from: createdAtDate) + } else { + return Self.createdAtRelativeFormatter.localizedString(for: createdAtDate, relativeTo: Date()) + } + } +} + +extension Calendar { + func numberOfDaysBetween(_ from: Date, and to: Date) -> Int { + let fromDate = startOfDay(for: from) + let toDate = startOfDay(for: to) + let numberOfDays = dateComponents([.day], from: fromDate, to: toDate) + + return numberOfDays.day! } } diff --git a/Packages/Models/Sources/Models/Status.swift b/Packages/Models/Sources/Models/Status.swift index 34bd33ad..da878e8a 100644 --- a/Packages/Models/Sources/Models/Status.swift +++ b/Packages/Models/Sources/Models/Status.swift @@ -1,6 +1,21 @@ import Foundation -public struct Status: Codable, Identifiable { +public protocol AnyStatus { + var id: String { get } + var content: String { get } + var account: Account { get } + var createdAt: String { get } +} + +public struct Status: AnyStatus, Codable, Identifiable { + public let id: String + public let content: String + public let account: Account + public let createdAt: String + public let reblog: ReblogStatus? +} + +public struct ReblogStatus: AnyStatus, Codable, Identifiable { public let id: String public let content: String public let account: Account diff --git a/Packages/Timeline/Sources/Timeline/Status/StatusActionsView.swift b/Packages/Timeline/Sources/Timeline/Status/StatusActionsView.swift new file mode 100644 index 00000000..975f24ae --- /dev/null +++ b/Packages/Timeline/Sources/Timeline/Status/StatusActionsView.swift @@ -0,0 +1,35 @@ +import SwiftUI +import Models +import Routeur + +struct StatusActionsView: View { + let status: Status + + var body: some View { + HStack { + Button { + + } label: { + Image(systemName: "bubble.right") + } + Spacer() + Button { + + } label: { + Image(systemName: "arrow.left.arrow.right.circle") + } + Spacer() + Button { + + } label: { + Image(systemName: "star") + } + Spacer() + Button { + + } label: { + Image(systemName: "square.and.arrow.up") + } + } + } +} diff --git a/Packages/Timeline/Sources/Timeline/Status/StatusRowView.swift b/Packages/Timeline/Sources/Timeline/Status/StatusRowView.swift index d0b3dd6d..c321631a 100644 --- a/Packages/Timeline/Sources/Timeline/Status/StatusRowView.swift +++ b/Packages/Timeline/Sources/Timeline/Status/StatusRowView.swift @@ -9,26 +9,45 @@ struct StatusRowView: View { var body: some View { VStack(alignment: .leading) { - HStack(alignment: .top) { - Button { - routeurPath.navigate(to: .accountDetail(id: status.account.id)) - } label: { - accountView - }.buttonStyle(.plain) - - Spacer() - Text(status.createdAtFormatted) - .font(.footnote) - .foregroundColor(.gray) - } - NavigationLink(value: RouteurDestinations.statusDetail(id: status.id)) { - Text(try! AttributedString(markdown: status.contentAsMarkdown)) - } + reblogView + statusView + StatusActionsView(status: status) + .padding(.vertical, 8) } } @ViewBuilder - private var accountView: some View { + private var reblogView: some View { + if status.reblog != nil { + HStack(spacing: 2) { + Image(systemName:"arrow.left.arrow.right.circle") + Text("\(status.account.displayName) reblogged") + } + .font(.footnote) + .foregroundColor(.gray) + .fontWeight(.semibold) + } + } + + @ViewBuilder + private var statusView: some View { + if let status: AnyStatus = status.reblog ?? status { + Button { + routeurPath.navigate(to: .accountDetail(id: status.account.id)) + } label: { + makeAccountView(status: status) + }.buttonStyle(.plain) + + Text(try! AttributedString(markdown: status.contentAsMarkdown)) + .font(.body) + .onTapGesture { + routeurPath.navigate(to: .statusDetail(id: status.id)) + } + } + } + + @ViewBuilder + private func makeAccountView(status: AnyStatus) -> some View { AsyncImage( url: status.account.avatar, content: { image in @@ -45,9 +64,15 @@ struct StatusRowView: View { VStack(alignment: .leading) { Text(status.account.displayName) .font(.headline) - Text("@\(status.account.acct)") - .font(.footnote) - .foregroundColor(.gray) + HStack { + Text("@\(status.account.acct)") + .font(.footnote) + .foregroundColor(.gray) + Spacer() + Text(status.createdAtFormatted) + .font(.footnote) + .foregroundColor(.gray) + } } } }