271 lines
9.7 KiB
Swift
271 lines
9.7 KiB
Swift
//
|
|
// MockStatusView.swift
|
|
// Tusker
|
|
//
|
|
// Created by Shadowfacts on 4/13/24.
|
|
// Copyright © 2024 Shadowfacts. All rights reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
import Pachyderm
|
|
import WebURL
|
|
|
|
struct MockStatusView: View {
|
|
@ObservedObject private var preferences = Preferences.shared
|
|
@ScaledMetric(relativeTo: .body) private var attachmentsLabelHeight = 17
|
|
|
|
var body: some View {
|
|
HStack(alignment: .top, spacing: 8) {
|
|
VStack(spacing: 4) {
|
|
Image("AboutIcon")
|
|
.resizable()
|
|
.clipShape(RoundedRectangle(cornerRadius: preferences.avatarStyle.cornerRadiusFraction * 50))
|
|
.frame(width: 50, height: 50)
|
|
|
|
MockMetaIndicatorsView()
|
|
|
|
Spacer()
|
|
}
|
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
HStack(spacing: 4) {
|
|
MockDisplayNameLabel()
|
|
Text(verbatim: "@tusker@example.com")
|
|
.foregroundStyle(.secondary)
|
|
.font(.body.weight(.light))
|
|
.lineLimit(1)
|
|
.truncationMode(.tail)
|
|
.layoutPriority(-100)
|
|
Spacer()
|
|
Text("1h")
|
|
.foregroundStyle(.secondary)
|
|
.font(.body.weight(.light))
|
|
}
|
|
|
|
MockStatusContentView()
|
|
|
|
if preferences.showLinkPreviews {
|
|
MockStatusCardView()
|
|
.frame(height: StatusContentContainer.cardViewHeight)
|
|
}
|
|
|
|
MockAttachmentsContainerView()
|
|
.aspectRatio(preferences.showAttachmentsInTimeline ? 16/9 : nil, contentMode: .fill)
|
|
.frame(height: preferences.showAttachmentsInTimeline ? nil : attachmentsLabelHeight)
|
|
.padding(.bottom, preferences.showAttachmentsInTimeline && preferences.hideActionsInTimeline ? 8 : 0)
|
|
|
|
if !preferences.hideActionsInTimeline {
|
|
MockStatusActionButtons()
|
|
}
|
|
}
|
|
.layoutPriority(100)
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct MockMetaIndicatorsView: UIViewRepresentable {
|
|
@ObservedObject private var preferences = Preferences.shared
|
|
|
|
func makeUIView(context: Context) -> StatusMetaIndicatorsView {
|
|
let view = StatusMetaIndicatorsView()
|
|
view.primaryAxis = .vertical
|
|
view.secondaryAxisAlignment = .trailing
|
|
return view
|
|
}
|
|
|
|
func updateUIView(_ uiView: StatusMetaIndicatorsView, context: Context) {
|
|
var indicators: StatusMetaIndicatorsView.Indicator = []
|
|
if preferences.showIsStatusReplyIcon {
|
|
indicators.insert(.reply)
|
|
}
|
|
if preferences.alwaysShowStatusVisibilityIcon {
|
|
indicators.insert(.visibility)
|
|
}
|
|
uiView.setIndicators(indicators, visibility: .public)
|
|
}
|
|
}
|
|
|
|
private struct MockDisplayNameLabel: View {
|
|
@ObservedObject private var preferences = Preferences.shared
|
|
@ScaledMetric(relativeTo: .body) private var emojiSize = 17
|
|
@State var textWithImage = Text("Tusker")
|
|
|
|
var body: some View {
|
|
displayName
|
|
.font(.body.weight(.semibold))
|
|
// don't let the height change depending on whether emojis are present or not
|
|
.frame(height: emojiSize)
|
|
.task(id: emojiSize) {
|
|
let size = CGSize(width: emojiSize, height: emojiSize)
|
|
let renderer = UIGraphicsImageRenderer(size: size)
|
|
let image = renderer.image { ctx in
|
|
let bounds = CGRect(origin: .zero, size: size)
|
|
UIBezierPath(roundedRect: bounds, cornerRadius: 2).addClip()
|
|
UIImage(named: "AboutIcon")!.draw(in: bounds)
|
|
}
|
|
textWithImage = Text("Tusker \(Image(uiImage: image))")
|
|
}
|
|
}
|
|
|
|
private var displayName: Text {
|
|
if preferences.hideCustomEmojiInUsernames {
|
|
Text("Tusker")
|
|
} else {
|
|
textWithImage
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct MockStatusContentView: View {
|
|
@ObservedObject private var preferences = Preferences.shared
|
|
|
|
var body: some View {
|
|
Text("This is an example post so you can check out how things look.\n\nThanks for using \(link)!")
|
|
.lineLimit(nil)
|
|
}
|
|
|
|
private var link: Text {
|
|
Text("Tusker")
|
|
.foregroundColor(.accentColor)
|
|
.underline(preferences.underlineTextLinks)
|
|
}
|
|
}
|
|
|
|
private struct MockStatusCardView: UIViewRepresentable {
|
|
func makeUIView(context: Context) -> StatusCardView {
|
|
let view = StatusCardView()
|
|
view.isUserInteractionEnabled = false
|
|
let card = Card(
|
|
url: WebURL("https://vaccor.space/tusker")!,
|
|
title: "Tusker",
|
|
description: "Tusker is an iOS app for Mastodon",
|
|
image: WebURL("https://vaccor.space/tusker/img/icon.png")!,
|
|
kind: .link
|
|
)
|
|
view.updateUI(card: card, sensitive: false)
|
|
return view
|
|
}
|
|
|
|
func updateUIView(_ uiView: StatusCardView, context: Context) {
|
|
}
|
|
}
|
|
|
|
private actor MockAttachmentsGenerator {
|
|
static let shared = MockAttachmentsGenerator()
|
|
|
|
private var attachmentURLs: [URL]?
|
|
|
|
func getAttachmentURLs(displayScale: CGFloat) -> [URL] {
|
|
if let attachmentURLs,
|
|
attachmentURLs.allSatisfy({ FileManager.default.fileExists(atPath: $0.path) }) {
|
|
return attachmentURLs
|
|
}
|
|
|
|
let size = CGSize(width: 200, height: 200)
|
|
let bounds = CGRect(origin: .zero, size: size)
|
|
let format = UIGraphicsImageRendererFormat()
|
|
format.scale = displayScale
|
|
let renderer = UIGraphicsImageRenderer(size: size, format: format)
|
|
|
|
let firstImage = renderer.image { ctx in
|
|
UIColor(red: 0x56 / 255, green: 0x03 / 255, blue: 0xad / 255, alpha: 1).setFill()
|
|
ctx.fill(bounds)
|
|
ctx.cgContext.concatenate(CGAffineTransform(1, 0, -0.5, 1, 0, 0))
|
|
for x in 0..<9 {
|
|
UIColor(red: 0x83 / 255, green: 0x67 / 255, blue: 0xc7 / 255, alpha: 1).setFill()
|
|
ctx.fill(CGRect(x: CGFloat(x) * 30 + 20, y: 0, width: 15, height: bounds.height))
|
|
}
|
|
}
|
|
let secondImage = renderer.image { ctx in
|
|
UIColor(red: 0x00 / 255, green: 0x43 / 255, blue: 0x85 / 255, alpha: 1).setFill()
|
|
ctx.fill(bounds)
|
|
UIColor(red: 0x05 / 255, green: 0xb2 / 255, blue: 0xdc / 255, alpha: 1).setFill()
|
|
for y in 0..<4 {
|
|
for x in 0..<5 {
|
|
let rect = CGRect(x: x * 45 - 5, y: y * 50 + 15, width: 20, height: 20)
|
|
ctx.cgContext.fillEllipse(in: rect)
|
|
}
|
|
}
|
|
UIColor(red: 0x08 / 255, green: 0x7c / 255, blue: 0xa7 / 255, alpha: 1).setFill()
|
|
for y in 0..<5 {
|
|
for x in 0..<4 {
|
|
let rect = CGRect(x: CGFloat(x) * 45 + 22.5, y: CGFloat(y) * 50 - 5, width: 10, height: 10)
|
|
ctx.cgContext.fillEllipse(in: rect)
|
|
}
|
|
}
|
|
}
|
|
|
|
let tempDirectory = FileManager.default.temporaryDirectory
|
|
let firstURL = tempDirectory.appendingPathComponent("\(UUID().description)", conformingTo: .png)
|
|
let secondURL = tempDirectory.appendingPathComponent("\(UUID().description)", conformingTo: .png)
|
|
|
|
do {
|
|
try firstImage.pngData()!.write(to: firstURL)
|
|
try secondImage.pngData()!.write(to: secondURL)
|
|
attachmentURLs = [firstURL, secondURL]
|
|
return [firstURL, secondURL]
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct MockAttachmentsContainerView: View {
|
|
@State private var attachments: [Attachment] = []
|
|
@Environment(\.displayScale) private var displayScale
|
|
|
|
var body: some View {
|
|
MockAttachmentsContainerRepresentable(attachments: attachments)
|
|
.task {
|
|
let attachmentURLs = await MockAttachmentsGenerator.shared.getAttachmentURLs(displayScale: displayScale)
|
|
self.attachments = [
|
|
.init(id: "1", kind: .image, url: attachmentURLs[0], description: "test"),
|
|
.init(id: "2", kind: .image, url: attachmentURLs[1], description: nil),
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct MockAttachmentsContainerRepresentable: UIViewRepresentable {
|
|
let attachments: [Attachment]
|
|
@ObservedObject private var preferences = Preferences.shared
|
|
|
|
func makeUIView(context: Context) -> AttachmentsContainerView {
|
|
let view = AttachmentsContainerView()
|
|
view.isUserInteractionEnabled = false
|
|
return view
|
|
}
|
|
|
|
func updateUIView(_ uiView: AttachmentsContainerView, context: Context) {
|
|
uiView.updateUI(attachments: attachments, labelOnly: !preferences.showAttachmentsInTimeline)
|
|
uiView.contentHidden = preferences.attachmentBlurMode == .always
|
|
for attachmentView in uiView.attachmentViews.allObjects {
|
|
attachmentView.updateBadges()
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct MockStatusActionButtons: View {
|
|
var body: some View {
|
|
HStack(spacing: 0) {
|
|
Image(systemName: "arrowshape.turn.up.left.fill")
|
|
.foregroundStyle(.tint)
|
|
Spacer()
|
|
Image(systemName: "star.fill")
|
|
.foregroundStyle(.tint)
|
|
Spacer()
|
|
Image(systemName: "repeat")
|
|
.foregroundStyle(.yellow)
|
|
Spacer()
|
|
Image(systemName: "ellipsis")
|
|
.foregroundStyle(.tint)
|
|
Spacer()
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
MockStatusView()
|
|
.frame(height: 300)
|
|
}
|