New default timeline layout
This commit is contained in:
parent
2e1652ef53
commit
1a3bded101
@ -260,9 +260,13 @@ struct DisplaySettingsView: View {
|
|||||||
Button {
|
Button {
|
||||||
theme.followSystemColorScheme = true
|
theme.followSystemColorScheme = true
|
||||||
theme.applySet(set: colorScheme == .dark ? .iceCubeDark : .iceCubeLight)
|
theme.applySet(set: colorScheme == .dark ? .iceCubeDark : .iceCubeLight)
|
||||||
theme.avatarShape = .rounded
|
theme.avatarShape = .circle
|
||||||
theme.avatarPosition = .top
|
theme.avatarPosition = .leading
|
||||||
theme.statusActionsDisplay = .full
|
theme.statusActionsDisplay = .full
|
||||||
|
theme.displayFullUsername = false
|
||||||
|
theme.statusDisplayStyle = .large
|
||||||
|
theme.lineSpacing = 1.2
|
||||||
|
theme.fontSizeScale = 1.0
|
||||||
|
|
||||||
localValues.tintColor = theme.tintColor
|
localValues.tintColor = theme.tintColor
|
||||||
localValues.primaryBackgroundColor = theme.primaryBackgroundColor
|
localValues.primaryBackgroundColor = theme.primaryBackgroundColor
|
||||||
|
@ -6,6 +6,7 @@ public extension CGFloat {
|
|||||||
static let dividerPadding: CGFloat = 2
|
static let dividerPadding: CGFloat = 2
|
||||||
static let scrollToViewHeight: CGFloat = 1
|
static let scrollToViewHeight: CGFloat = 1
|
||||||
static let statusColumnsSpacing: CGFloat = 8
|
static let statusColumnsSpacing: CGFloat = 8
|
||||||
|
static let statusComponentSpacing: CGFloat = 6
|
||||||
static let secondaryColumnWidth: CGFloat = 400
|
static let secondaryColumnWidth: CGFloat = 400
|
||||||
static let sidebarWidth: CGFloat = 90
|
static let sidebarWidth: CGFloat = 90
|
||||||
static let pollBarHeight: CGFloat = 30
|
static let pollBarHeight: CGFloat = 30
|
||||||
|
@ -18,14 +18,14 @@ import SwiftUI
|
|||||||
@AppStorage(ThemeKey.primaryBackground.rawValue) public var primaryBackgroundColor: Color = .white
|
@AppStorage(ThemeKey.primaryBackground.rawValue) public var primaryBackgroundColor: Color = .white
|
||||||
@AppStorage(ThemeKey.secondaryBackground.rawValue) public var secondaryBackgroundColor: Color = .gray
|
@AppStorage(ThemeKey.secondaryBackground.rawValue) public var secondaryBackgroundColor: Color = .gray
|
||||||
@AppStorage(ThemeKey.label.rawValue) public var labelColor: Color = .black
|
@AppStorage(ThemeKey.label.rawValue) public var labelColor: Color = .black
|
||||||
@AppStorage(ThemeKey.avatarPosition2.rawValue) var avatarPosition: AvatarPosition = .top
|
@AppStorage(ThemeKey.avatarPosition2.rawValue) var avatarPosition: AvatarPosition = .leading
|
||||||
@AppStorage(ThemeKey.avatarShape2.rawValue) var avatarShape: AvatarShape = .rounded
|
@AppStorage(ThemeKey.avatarShape2.rawValue) var avatarShape: AvatarShape = .circle
|
||||||
@AppStorage(ThemeKey.selectedSet.rawValue) var storedSet: ColorSetName = .iceCubeDark
|
@AppStorage(ThemeKey.selectedSet.rawValue) var storedSet: ColorSetName = .iceCubeDark
|
||||||
@AppStorage(ThemeKey.statusActionsDisplay.rawValue) public var statusActionsDisplay: StatusActionsDisplay = .full
|
@AppStorage(ThemeKey.statusActionsDisplay.rawValue) public var statusActionsDisplay: StatusActionsDisplay = .full
|
||||||
@AppStorage(ThemeKey.statusDisplayStyle.rawValue) public var statusDisplayStyle: StatusDisplayStyle = .large
|
@AppStorage(ThemeKey.statusDisplayStyle.rawValue) public var statusDisplayStyle: StatusDisplayStyle = .large
|
||||||
@AppStorage(ThemeKey.followSystemColorSchme.rawValue) public var followSystemColorScheme: Bool = true
|
@AppStorage(ThemeKey.followSystemColorSchme.rawValue) public var followSystemColorScheme: Bool = true
|
||||||
@AppStorage(ThemeKey.displayFullUsernameTimeline.rawValue) public var displayFullUsername: Bool = true
|
@AppStorage(ThemeKey.displayFullUsernameTimeline.rawValue) public var displayFullUsername: Bool = false
|
||||||
@AppStorage(ThemeKey.lineSpacing.rawValue) public var lineSpacing: Double = 0.8
|
@AppStorage(ThemeKey.lineSpacing.rawValue) public var lineSpacing: Double = 1.2
|
||||||
@AppStorage("font_size_scale") public var fontSizeScale: Double = 1
|
@AppStorage("font_size_scale") public var fontSizeScale: Double = 1
|
||||||
@AppStorage("chosen_font") public var chosenFontData: Data?
|
@AppStorage("chosen_font") public var chosenFontData: Data?
|
||||||
|
|
||||||
|
@ -10,6 +10,8 @@ class DateFormatterCache: @unchecked Sendable {
|
|||||||
init() {
|
init() {
|
||||||
let createdAtRelativeFormatter = RelativeDateTimeFormatter()
|
let createdAtRelativeFormatter = RelativeDateTimeFormatter()
|
||||||
createdAtRelativeFormatter.unitsStyle = .short
|
createdAtRelativeFormatter.unitsStyle = .short
|
||||||
|
createdAtRelativeFormatter.formattingContext = .listItem
|
||||||
|
createdAtRelativeFormatter.dateTimeStyle = .numeric
|
||||||
self.createdAtRelativeFormatter = createdAtRelativeFormatter
|
self.createdAtRelativeFormatter = createdAtRelativeFormatter
|
||||||
|
|
||||||
let createdAtShortDateFormatted = DateFormatter()
|
let createdAtShortDateFormatted = DateFormatter()
|
||||||
|
@ -38,7 +38,10 @@ public struct ConsolidatedNotification: Identifiable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static func placeholders() -> [ConsolidatedNotification] {
|
public static func placeholders() -> [ConsolidatedNotification] {
|
||||||
[.placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder()]
|
[.placeholder(), .placeholder(), .placeholder(),
|
||||||
|
.placeholder(), .placeholder(), .placeholder(),
|
||||||
|
.placeholder(), .placeholder(), .placeholder(),
|
||||||
|
.placeholder(), .placeholder(), .placeholder()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +148,8 @@ public final class Status: AnyStatus, Codable, Identifiable, Equatable, Hashable
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static func placeholders() -> [Status] {
|
public static func placeholders() -> [Status] {
|
||||||
[.placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder()]
|
[.placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder(),
|
||||||
|
.placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder()]
|
||||||
}
|
}
|
||||||
|
|
||||||
public var reblogAsAsStatus: Status? {
|
public var reblogAsAsStatus: Status? {
|
||||||
|
@ -94,44 +94,50 @@ struct NotificationRowView: View {
|
|||||||
.frame(height: AvatarView.FrameConfig.status.size.height + 2)
|
.frame(height: AvatarView.FrameConfig.status.size.height + 2)
|
||||||
}.offset(y: -1)
|
}.offset(y: -1)
|
||||||
}
|
}
|
||||||
HStack(spacing: 0) {
|
if reasons.isEmpty {
|
||||||
EmojiTextApp(.init(stringValue: notification.accounts[0].safeDisplayName),
|
HStack(spacing: 0) {
|
||||||
emojis: notification.accounts[0].emojis,
|
EmojiTextApp(.init(stringValue: notification.accounts[0].safeDisplayName),
|
||||||
append: {
|
emojis: notification.accounts[0].emojis,
|
||||||
(notification.accounts.count > 1
|
append: {
|
||||||
? Text("notifications-others-count \(notification.accounts.count - 1)")
|
(notification.accounts.count > 1
|
||||||
.font(.scaledSubheadline)
|
? Text("notifications-others-count \(notification.accounts.count - 1)")
|
||||||
.fontWeight(.regular)
|
.font(.scaledSubheadline)
|
||||||
: Text(" ")) +
|
.fontWeight(.regular)
|
||||||
Text(type.label(count: notification.accounts.count))
|
: Text(" ")) +
|
||||||
.font(.scaledSubheadline)
|
Text(type.label(count: notification.accounts.count))
|
||||||
.fontWeight(.regular) +
|
.font(.scaledSubheadline)
|
||||||
Text(" ⸱ ")
|
.fontWeight(.regular) +
|
||||||
.font(.scaledFootnote)
|
Text(" ⸱ ")
|
||||||
.fontWeight(.regular)
|
.font(.scaledFootnote)
|
||||||
.foregroundStyle(.secondary) +
|
.fontWeight(.regular)
|
||||||
Text(notification.createdAt.relativeFormatted)
|
.foregroundStyle(.secondary) +
|
||||||
.font(.scaledFootnote)
|
Text(notification.createdAt.relativeFormatted)
|
||||||
.fontWeight(.regular)
|
.font(.scaledFootnote)
|
||||||
.foregroundStyle(.secondary)
|
.fontWeight(.regular)
|
||||||
})
|
.foregroundStyle(.secondary)
|
||||||
.font(.scaledSubheadline)
|
})
|
||||||
.emojiSize(Font.scaledSubheadlineFont.emojiSize)
|
.font(.scaledSubheadline)
|
||||||
.emojiBaselineOffset(Font.scaledSubheadlineFont.emojiBaselineOffset)
|
.emojiSize(Font.scaledSubheadlineFont.emojiSize)
|
||||||
.fontWeight(.semibold)
|
.emojiBaselineOffset(Font.scaledSubheadlineFont.emojiBaselineOffset)
|
||||||
.lineLimit(3)
|
.fontWeight(.semibold)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.lineLimit(3)
|
||||||
if let status = notification.status, notification.type == .mention {
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
Group {
|
if let status = notification.status, notification.type == .mention {
|
||||||
Text(" ⸱ ")
|
Group {
|
||||||
Text(Image(systemName: status.visibility.iconName))
|
Text(" ⸱ ")
|
||||||
|
Text(Image(systemName: status.visibility.iconName))
|
||||||
|
}
|
||||||
|
.accessibilityHidden(true)
|
||||||
|
.font(.scaledFootnote)
|
||||||
|
.fontWeight(.regular)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
.accessibilityHidden(true)
|
Spacer()
|
||||||
.font(.scaledFootnote)
|
|
||||||
.fontWeight(.regular)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
}
|
||||||
Spacer()
|
} else {
|
||||||
|
Text(" ")
|
||||||
|
.font(.scaledSubheadline)
|
||||||
|
.fontWeight(.semibold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
|
@ -48,7 +48,7 @@ public struct StatusRowView: View {
|
|||||||
Spacer(minLength: 8)
|
Spacer(minLength: 8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading, spacing: .statusComponentSpacing) {
|
||||||
if viewModel.isFiltered, let filter = viewModel.filter {
|
if viewModel.isFiltered, let filter = viewModel.filter {
|
||||||
switch filter.filter.filterAction {
|
switch filter.filter.filterAction {
|
||||||
case .warn:
|
case .warn:
|
||||||
@ -74,7 +74,7 @@ public struct StatusRowView: View {
|
|||||||
AvatarView(viewModel.finalStatus.account.avatar)
|
AvatarView(viewModel.finalStatus.account.avatar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading, spacing: .statusComponentSpacing) {
|
||||||
if !isCompact, theme.avatarPosition == .top {
|
if !isCompact, theme.avatarPosition == .top {
|
||||||
StatusRowReblogView(viewModel: viewModel)
|
StatusRowReblogView(viewModel: viewModel)
|
||||||
StatusRowReplyView(viewModel: viewModel)
|
StatusRowReplyView(viewModel: viewModel)
|
||||||
@ -82,37 +82,31 @@ public struct StatusRowView: View {
|
|||||||
StatusRowTagView(viewModel: viewModel)
|
StatusRowTagView(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
if !isCompact {
|
||||||
if !isCompact {
|
StatusRowHeaderView(viewModel: viewModel)
|
||||||
StatusRowHeaderView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
StatusRowContentView(viewModel: viewModel)
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
.onTapGesture {
|
|
||||||
guard !isFocused else { return }
|
|
||||||
viewModel.navigateToDetail()
|
|
||||||
}
|
|
||||||
.accessibilityActions {
|
|
||||||
if isFocused, viewModel.showActions {
|
|
||||||
accessibilityActions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
StatusRowContentView(viewModel: viewModel)
|
||||||
if viewModel.showActions, isFocused || theme.statusActionsDisplay != .none, !isInCaptureMode {
|
.contentShape(Rectangle())
|
||||||
StatusRowActionsView(viewModel: viewModel)
|
.onTapGesture {
|
||||||
.padding(.top, 8)
|
guard !isFocused else { return }
|
||||||
.tint(isFocused ? theme.tintColor : .gray)
|
viewModel.navigateToDetail()
|
||||||
.contentShape(Rectangle())
|
|
||||||
.onTapGesture {
|
|
||||||
guard !isFocused else { return }
|
|
||||||
viewModel.navigateToDetail()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.accessibilityActions {
|
||||||
|
if isFocused, viewModel.showActions {
|
||||||
|
accessibilityActions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reasons.isEmpty,
|
||||||
|
viewModel.showActions, isFocused || theme.statusActionsDisplay != .none,
|
||||||
|
!isInCaptureMode {
|
||||||
|
StatusRowActionsView(viewModel: viewModel)
|
||||||
|
.tint(isFocused ? theme.tintColor : .gray)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.padding(.top, 6)
|
||||||
|
}
|
||||||
|
|
||||||
if isFocused, !isCompact {
|
if isFocused, !isCompact {
|
||||||
StatusRowDetailView(viewModel: viewModel)
|
StatusRowDetailView(viewModel: viewModel)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ struct StatusRowActionsView: View {
|
|||||||
@Environment(\.openWindow) private var openWindow
|
@Environment(\.openWindow) private var openWindow
|
||||||
@Environment(\.isStatusFocused) private var isFocused
|
@Environment(\.isStatusFocused) private var isFocused
|
||||||
|
|
||||||
|
@State private var showTextForSelection: Bool = false
|
||||||
|
|
||||||
var viewModel: StatusRowViewModel
|
var viewModel: StatusRowViewModel
|
||||||
|
|
||||||
func privateBoost() -> Bool {
|
func privateBoost() -> Bool {
|
||||||
@ -22,7 +24,7 @@ struct StatusRowActionsView: View {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
enum Action: CaseIterable {
|
enum Action: CaseIterable {
|
||||||
case respond, boost, favorite, bookmark, share
|
case respond, boost, favorite, bookmark, share, menu
|
||||||
|
|
||||||
// Have to implement this manually here due to compiler not implicitly
|
// Have to implement this manually here due to compiler not implicitly
|
||||||
// inserting `nonisolated`, which leads to a warning:
|
// inserting `nonisolated`, which leads to a warning:
|
||||||
@ -31,7 +33,7 @@ struct StatusRowActionsView: View {
|
|||||||
// satisfy nonisolated protocol requirement
|
// satisfy nonisolated protocol requirement
|
||||||
//
|
//
|
||||||
public nonisolated static var allCases: [StatusRowActionsView.Action] {
|
public nonisolated static var allCases: [StatusRowActionsView.Action] {
|
||||||
[.respond, .boost, .favorite, .bookmark, .share]
|
[.respond, .boost, .favorite, .share, .menu]
|
||||||
}
|
}
|
||||||
|
|
||||||
func image(dataController: StatusDataController, privateBoost: Bool = false) -> Image {
|
func image(dataController: StatusDataController, privateBoost: Bool = false) -> Image {
|
||||||
@ -53,6 +55,8 @@ struct StatusRowActionsView: View {
|
|||||||
return Image(systemName: dataController.isBookmarked ? "bookmark.fill" : "bookmark")
|
return Image(systemName: dataController.isBookmarked ? "bookmark.fill" : "bookmark")
|
||||||
case .share:
|
case .share:
|
||||||
return Image(systemName: "square.and.arrow.up")
|
return Image(systemName: "square.and.arrow.up")
|
||||||
|
case .menu:
|
||||||
|
return Image(systemName: "ellipsis")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +81,8 @@ struct StatusRowActionsView: View {
|
|||||||
: "status.action.bookmark"
|
: "status.action.bookmark"
|
||||||
case .share:
|
case .share:
|
||||||
return "status.action.share"
|
return "status.action.share"
|
||||||
|
case .menu:
|
||||||
|
return "status.context.menu"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,14 +97,14 @@ struct StatusRowActionsView: View {
|
|||||||
return dataController.favoritesCount
|
return dataController.favoritesCount
|
||||||
case .boost:
|
case .boost:
|
||||||
return dataController.reblogsCount
|
return dataController.reblogsCount
|
||||||
case .share, .bookmark:
|
case .share, .bookmark, .menu:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tintColor(theme: Theme) -> Color? {
|
func tintColor(theme: Theme) -> Color? {
|
||||||
switch self {
|
switch self {
|
||||||
case .respond, .share:
|
case .respond, .share, .menu:
|
||||||
nil
|
nil
|
||||||
case .favorite:
|
case .favorite:
|
||||||
.yellow
|
.yellow
|
||||||
@ -111,7 +117,7 @@ struct StatusRowActionsView: View {
|
|||||||
|
|
||||||
func isOn(dataController: StatusDataController) -> Bool {
|
func isOn(dataController: StatusDataController) -> Bool {
|
||||||
switch self {
|
switch self {
|
||||||
case .respond, .share: false
|
case .respond, .share, .menu: false
|
||||||
case .favorite: dataController.isFavorited
|
case .favorite: dataController.isFavorited
|
||||||
case .bookmark: dataController.isBookmarked
|
case .bookmark: dataController.isBookmarked
|
||||||
case .boost: dataController.isReblogged
|
case .boost: dataController.isReblogged
|
||||||
@ -147,6 +153,22 @@ struct StatusRowActionsView: View {
|
|||||||
.accessibilityLabel("status.action.share-link")
|
.accessibilityLabel("status.action.share-link")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Spacer()
|
||||||
|
} else if action == .menu {
|
||||||
|
Menu {
|
||||||
|
StatusRowContextMenu(viewModel: viewModel, showTextForSelection: $showTextForSelection)
|
||||||
|
.onAppear {
|
||||||
|
Task {
|
||||||
|
await viewModel.loadAuthorRelationship()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label("", systemImage: "ellipsis")
|
||||||
|
}
|
||||||
|
.menuStyle(.button)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.accessibilityLabel("status.action.context-menu")
|
||||||
} else {
|
} else {
|
||||||
actionButton(action: action)
|
actionButton(action: action)
|
||||||
Spacer()
|
Spacer()
|
||||||
@ -154,6 +176,10 @@ struct StatusRowActionsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $showTextForSelection) {
|
||||||
|
let content = viewModel.status.reblog?.content.asSafeMarkdownAttributedString ?? viewModel.status.content.asSafeMarkdownAttributedString
|
||||||
|
SelectTextView(content: content)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func actionButton(action: Action) -> some View {
|
private func actionButton(action: Action) -> some View {
|
||||||
|
@ -20,7 +20,9 @@ struct StatusRowContentView: View {
|
|||||||
|
|
||||||
if !viewModel.displaySpoiler {
|
if !viewModel.displaySpoiler {
|
||||||
StatusRowTextView(viewModel: viewModel)
|
StatusRowTextView(viewModel: viewModel)
|
||||||
StatusRowTranslateView(viewModel: viewModel)
|
if reasons.isEmpty {
|
||||||
|
StatusRowTranslateView(viewModel: viewModel)
|
||||||
|
}
|
||||||
if let poll = viewModel.finalStatus.poll {
|
if let poll = viewModel.finalStatus.poll {
|
||||||
StatusPollView(poll: poll, status: viewModel.finalStatus)
|
StatusPollView(poll: poll, status: viewModel.finalStatus)
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,19 @@ import DesignSystem
|
|||||||
import Env
|
import Env
|
||||||
import Models
|
import Models
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Network
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct StatusRowHeaderView: View {
|
struct StatusRowHeaderView: View {
|
||||||
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
|
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
|
||||||
@Environment(\.isStatusFocused) private var isFocused
|
@Environment(\.isStatusFocused) private var isFocused
|
||||||
|
@Environment(\.redactionReasons) private var redactionReasons
|
||||||
|
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
|
|
||||||
let viewModel: StatusRowViewModel
|
let viewModel: StatusRowViewModel
|
||||||
@State private var showTextForSelection: Bool = false
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: theme.avatarPosition == .top ? .center : .top) {
|
||||||
Button {
|
Button {
|
||||||
viewModel.navigateToAccountDetail(account: viewModel.finalStatus.account)
|
viewModel.navigateToAccountDetail(account: viewModel.finalStatus.account)
|
||||||
} label: {
|
} label: {
|
||||||
@ -24,23 +24,13 @@ struct StatusRowHeaderView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
if !isInCaptureMode {
|
if !isInCaptureMode {
|
||||||
threadIcon
|
threadIcon
|
||||||
contextMenuButton
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showTextForSelection) {
|
|
||||||
let content = viewModel.status.reblog?.content.asSafeMarkdownAttributedString ?? viewModel.status.content.asSafeMarkdownAttributedString
|
|
||||||
SelectTextView(content: content)
|
|
||||||
}
|
|
||||||
.accessibilityElement(children: .combine)
|
.accessibilityElement(children: .combine)
|
||||||
.accessibilityLabel(Text("\(viewModel.finalStatus.account.safeDisplayName)") + Text(", ") + Text(viewModel.finalStatus.createdAt.relativeFormatted))
|
.accessibilityLabel(Text("\(viewModel.finalStatus.account.safeDisplayName)") + Text(", ") + Text(viewModel.finalStatus.createdAt.relativeFormatted))
|
||||||
.accessibilityAction {
|
.accessibilityAction {
|
||||||
viewModel.navigateToAccountDetail(account: viewModel.finalStatus.account)
|
viewModel.navigateToAccountDetail(account: viewModel.finalStatus.account)
|
||||||
}
|
}
|
||||||
.accessibilityActions {
|
|
||||||
if isFocused {
|
|
||||||
StatusRowContextMenu(viewModel: viewModel, showTextForSelection: $showTextForSelection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
@ -63,28 +53,26 @@ struct StatusRowHeaderView: View {
|
|||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.accountPopover(viewModel.finalStatus.account)
|
.accountPopover(viewModel.finalStatus.account)
|
||||||
|
|
||||||
accountBadgeView
|
if redactionReasons.isEmpty {
|
||||||
.font(.footnote)
|
accountBadgeView
|
||||||
|
.font(.footnote)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.layoutPriority(1)
|
.layoutPriority(1)
|
||||||
if theme.avatarPosition == .leading {
|
if redactionReasons.isEmpty {
|
||||||
dateView
|
if theme.avatarPosition == .leading {
|
||||||
.font(.scaledFootnote)
|
dateView
|
||||||
.foregroundStyle(.secondary)
|
} else {
|
||||||
.lineLimit(1)
|
Text("@\(theme.displayFullUsername ? viewModel.finalStatus.account.acct : viewModel.finalStatus.account.username)")
|
||||||
} else {
|
.font(.scaledFootnote)
|
||||||
Text("@\(theme.displayFullUsername ? viewModel.finalStatus.account.acct : viewModel.finalStatus.account.username)")
|
.foregroundStyle(.secondary)
|
||||||
.font(.scaledFootnote)
|
.lineLimit(1)
|
||||||
.foregroundStyle(.secondary)
|
.accountPopover(viewModel.finalStatus.account)
|
||||||
.lineLimit(1)
|
}
|
||||||
.accountPopover(viewModel.finalStatus.account)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if theme.avatarPosition == .top {
|
if theme.avatarPosition == .top {
|
||||||
dateView
|
dateView
|
||||||
.font(.scaledFootnote)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
.lineLimit(1)
|
|
||||||
} else if theme.displayFullUsername, theme.avatarPosition == .leading {
|
} else if theme.displayFullUsername, theme.avatarPosition == .leading {
|
||||||
Text("@\(viewModel.finalStatus.account.acct)")
|
Text("@\(viewModel.finalStatus.account.acct)")
|
||||||
.font(.scaledFootnote)
|
.font(.scaledFootnote)
|
||||||
@ -106,10 +94,15 @@ struct StatusRowHeaderView: View {
|
|||||||
return Text("")
|
return Text("")
|
||||||
}
|
}
|
||||||
|
|
||||||
private var dateView: Text {
|
private var dateView: some View {
|
||||||
Text(viewModel.finalStatus.createdAt.relativeFormatted) +
|
Group {
|
||||||
Text(" ⸱ ") +
|
Text(viewModel.finalStatus.createdAt.relativeFormatted) +
|
||||||
Text(Image(systemName: viewModel.finalStatus.visibility.iconName))
|
Text(" ⸱ ") +
|
||||||
|
Text(Image(systemName: viewModel.finalStatus.visibility.iconName))
|
||||||
|
}
|
||||||
|
.font(.scaledFootnote)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
@ -122,22 +115,4 @@ struct StatusRowHeaderView: View {
|
|||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var contextMenuButton: some View {
|
|
||||||
Menu {
|
|
||||||
StatusRowContextMenu(viewModel: viewModel, showTextForSelection: $showTextForSelection)
|
|
||||||
.onAppear {
|
|
||||||
Task {
|
|
||||||
await viewModel.loadAuthorRelationship()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "ellipsis")
|
|
||||||
.frame(width: 40, height: 40)
|
|
||||||
}
|
|
||||||
.menuStyle(.borderlessButton)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
.accessibilityHidden(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -149,6 +149,8 @@ private struct MediaPreview: View {
|
|||||||
image
|
image
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(width: displayData.isLandscape ? imageMaxHeight * 1.2 : imageMaxHeight / 1.5,
|
||||||
|
height: imageMaxHeight)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 4)
|
RoundedRectangle(cornerRadius: 4)
|
||||||
.stroke(.gray.opacity(0.35), lineWidth: 1)
|
.stroke(.gray.opacity(0.35), lineWidth: 1)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user