A bit better timeline UI
This commit is contained in:
parent
b7ce9648d5
commit
eb4dc011b6
|
@ -1,7 +1,7 @@
|
||||||
import HTML2Markdown
|
import HTML2Markdown
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension Status {
|
extension AnyStatus {
|
||||||
private static var createdAtDateFormatter: DateFormatter {
|
private static var createdAtDateFormatter: DateFormatter {
|
||||||
let dateFormatter = DateFormatter()
|
let dateFormatter = DateFormatter()
|
||||||
dateFormatter.calendar = .init(identifier: .iso8601)
|
dateFormatter.calendar = .init(identifier: .iso8601)
|
||||||
|
@ -16,6 +16,12 @@ extension Status {
|
||||||
return dateFormatter
|
return dateFormatter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static var createdAtShortDateFormatted: DateFormatter {
|
||||||
|
let dateFormatter = DateFormatter()
|
||||||
|
dateFormatter.dateStyle = .medium
|
||||||
|
return dateFormatter
|
||||||
|
}
|
||||||
|
|
||||||
public var contentAsMarkdown: String {
|
public var contentAsMarkdown: String {
|
||||||
do {
|
do {
|
||||||
let dom = try HTMLParser().parse(html: content)
|
let dom = try HTMLParser().parse(html: content)
|
||||||
|
@ -30,6 +36,21 @@ extension Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
public var createdAtFormatted: String {
|
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!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,21 @@
|
||||||
import Foundation
|
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 id: String
|
||||||
public let content: String
|
public let content: String
|
||||||
public let account: Account
|
public let account: Account
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,26 +9,45 @@ struct StatusRowView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack(alignment: .top) {
|
reblogView
|
||||||
Button {
|
statusView
|
||||||
routeurPath.navigate(to: .accountDetail(id: status.account.id))
|
StatusActionsView(status: status)
|
||||||
} label: {
|
.padding(.vertical, 8)
|
||||||
accountView
|
|
||||||
}.buttonStyle(.plain)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
Text(status.createdAtFormatted)
|
|
||||||
.font(.footnote)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
NavigationLink(value: RouteurDestinations.statusDetail(id: status.id)) {
|
|
||||||
Text(try! AttributedString(markdown: status.contentAsMarkdown))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@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(
|
AsyncImage(
|
||||||
url: status.account.avatar,
|
url: status.account.avatar,
|
||||||
content: { image in
|
content: { image in
|
||||||
|
@ -45,9 +64,15 @@ struct StatusRowView: View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text(status.account.displayName)
|
Text(status.account.displayName)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
Text("@\(status.account.acct)")
|
HStack {
|
||||||
.font(.footnote)
|
Text("@\(status.account.acct)")
|
||||||
.foregroundColor(.gray)
|
.font(.footnote)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
Spacer()
|
||||||
|
Text(status.createdAtFormatted)
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue