diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/questionmark.circle.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/questionmark.circle.imageset/Contents.json new file mode 100644 index 000000000..e09f6367e --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/questionmark.circle.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "questionmark.circle.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/questionmark.circle.imageset/questionmark.circle.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/questionmark.circle.imageset/questionmark.circle.pdf new file mode 100644 index 000000000..8a9014fd0 Binary files /dev/null and b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/questionmark.circle.imageset/questionmark.circle.pdf differ diff --git a/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift b/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift index 9adc96666..8070a1349 100644 --- a/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift +++ b/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift @@ -154,6 +154,7 @@ public enum Asset { public static let peopleAdd = ImageAsset(name: "Scene/Compose/people.add") public static let pollFill = ImageAsset(name: "Scene/Compose/poll.fill") public static let poll = ImageAsset(name: "Scene/Compose/poll") + public static let questionmarkCircle = ImageAsset(name: "Scene/Compose/questionmark.circle") public static let reorderDot = ImageAsset(name: "Scene/Compose/reorder.dot") } public enum Discovery { diff --git a/MastodonSDK/Sources/MastodonUI/Extension/MastodonVisibility+Image.swift b/MastodonSDK/Sources/MastodonUI/Extension/MastodonVisibility+Image.swift new file mode 100644 index 000000000..05dcd5e18 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Extension/MastodonVisibility+Image.swift @@ -0,0 +1,20 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import UIKit +import CoreDataStack +import MastodonAsset + +extension MastodonVisibility { + + public var image: UIImage { + let asset: ImageAsset + switch self { + case .public: asset = Asset.Scene.Compose.earth + case .unlisted: asset = Asset.Scene.Compose.people + case .private: asset = Asset.Scene.Compose.peopleAdd + case .direct: asset = Asset.Scene.Compose.mention + case ._other: asset = Asset.Scene.Compose.questionmarkCircle + } + return asset.image.withRenderingMode(.alwaysTemplate) + } +} diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewController.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewController.swift index 787847705..9187f746e 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewController.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewController.swift @@ -126,7 +126,6 @@ extension ComposeContentViewController { toolbarHostingView.view.heightAnchor.constraint(equalToConstant: ComposeContentToolbarView.toolbarHeight), ]) toolbarHostingView.view.preservesSuperviewLayoutMargins = true - //composeToolbarView.delegate = self composeContentToolbarBackgroundView.translatesAutoresizingMaskIntoConstraints = false view.insertSubview(composeContentToolbarBackgroundView, belowSubview: toolbarHostingView.view) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift index 68b13fed5..f37ae28b4 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift @@ -43,6 +43,8 @@ extension NotificationView { @Published public var timestamp: Date? + @Published public var visibility: MastodonVisibility = .public + @Published public var followRequestState = MastodonFollowRequestState(state: .none) @Published public var transientFollowRequestState = MastodonFollowRequestState(state: .none) @@ -122,6 +124,12 @@ extension NotificationView.ViewModel { } .store(in: &disposeBag) + $visibility + .sink { visibility in + notificationView.visibilityIconImageView.image = visibility.image + } + .store(in: &disposeBag) + // notification type indicator $notificationIndicatorText .sink { text in diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift index 88956d9be..47b9ea65e 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift @@ -91,6 +91,20 @@ public final class NotificationView: UIView { // timestamp public let dateLabel = MetaLabel(style: .statusUsername) + public let dateTrailingDotLabel: MetaLabel = { + let label = MetaLabel(style: .statusUsername) + label.configure(content: PlaintextMetaContent(string: "·")) + return label + }() + + let visibilityIconImageView: UIImageView = { + let imageView = UIImageView() + imageView.tintColor = Asset.Colors.Label.secondary.color + imageView.contentMode = .scaleAspectFit + imageView.image = Asset.Scene.Compose.earth.image.withRenderingMode(.alwaysTemplate) + return imageView + }() + public let menuButton: UIButton = { let button = HitTestExpandedButton(type: .system) button.tintColor = Asset.Colors.Label.secondary.color @@ -272,16 +286,32 @@ extension NotificationView { authrMetaContainer.setCustomSpacing(4, after: authorSecondaryMetaContainer) authorSecondaryMetaContainer.addArrangedSubview(authorUsernameLabel) - authorUsernameLabel.setContentHuggingPriority(.required - 8, for: .horizontal) - authorUsernameLabel.setContentCompressionResistancePriority(.required - 8, for: .horizontal) + authorUsernameLabel.setContentHuggingPriority(.required - 1, for: .vertical) + authorUsernameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + authorSecondaryMetaContainer.addArrangedSubview(usernameTrialingDotLabel) - usernameTrialingDotLabel.setContentHuggingPriority(.required - 2, for: .horizontal) - usernameTrialingDotLabel.setContentCompressionResistancePriority(.required - 2, for: .horizontal) + usernameTrialingDotLabel.setContentCompressionResistancePriority(.required, for: .horizontal) + authorSecondaryMetaContainer.addArrangedSubview(dateLabel) dateLabel.setContentHuggingPriority(.required - 1, for: .horizontal) dateLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal) + + authorSecondaryMetaContainer.addArrangedSubview(dateTrailingDotLabel) + dateTrailingDotLabel.setContentCompressionResistancePriority(.required, for: .horizontal) + + authorSecondaryMetaContainer.addArrangedSubview(dateTrailingDotLabel) + dateTrailingDotLabel.setContentCompressionResistancePriority(.required, for: .horizontal) + + authorSecondaryMetaContainer.addArrangedSubview(visibilityIconImageView) + NSLayoutConstraint.activate([ + visibilityIconImageView.heightAnchor.constraint(equalTo: authorUsernameLabel.heightAnchor), + visibilityIconImageView.widthAnchor.constraint(equalTo: visibilityIconImageView.heightAnchor), + ]) + authorSecondaryMetaContainer.addArrangedSubview(UIView()) + authorSecondaryMetaContainer.setCustomSpacing(0, after: visibilityIconImageView) + // authorContainerViewBottomPaddingView authorContainerViewBottomPaddingView.translatesAutoresizingMaskIntoConstraints = false containerStackView.addArrangedSubview(authorContainerViewBottomPaddingView) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift index a5cb3e808..6d3abd749 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift @@ -41,9 +41,23 @@ public class StatusAuthorView: UIStackView { // timestamp public let dateLabel = MetaLabel(style: .statusUsername) + public let dateTrailingDotLabel: MetaLabel = { + let label = MetaLabel(style: .statusUsername) + label.configure(content: PlaintextMetaContent(string: "·")) + return label + }() + + let visibilityIconImageView: UIImageView = { + let imageView = UIImageView() + imageView.tintColor = Asset.Colors.Label.secondary.color + imageView.contentMode = .scaleAspectFit + imageView.image = Asset.Scene.Compose.earth.image.withRenderingMode(.alwaysTemplate) + return imageView + }() + public let menuButton: UIButton = { let button = HitTestExpandedButton(type: .system) - button.expandEdgeInsets = UIEdgeInsets(top: -20, left: -10, bottom: -5, right: -10) + button.expandEdgeInsets = UIEdgeInsets(top: -20, left: -10, bottom: -10, right: -10) button.tintColor = Asset.Colors.Label.secondary.color let image = UIImage(systemName: "ellipsis", withConfiguration: UIImage.SymbolConfiguration(font: .systemFont(ofSize: 15))) button.setImage(image, for: .normal) @@ -53,11 +67,10 @@ public class StatusAuthorView: UIStackView { public let contentSensitiveeToggleButton: UIButton = { let button = HitTestExpandedButton(type: .system) - button.expandEdgeInsets = UIEdgeInsets(top: -5, left: -10, bottom: -20, right: -10) + button.expandEdgeInsets = UIEdgeInsets(top: -20, left: -10, bottom: -10, right: -10) button.tintColor = Asset.Colors.Label.secondary.color - button.imageView?.contentMode = .scaleAspectFill - button.imageView?.clipsToBounds = false - let image = UIImage(systemName: "eye.slash.fill", withConfiguration: UIImage.SymbolConfiguration(font: .systemFont(ofSize: 15))) + button.imageView?.contentMode = .scaleAspectFit + let image = UIImage(systemName: "eye.slash.fill") button.setImage(image, for: .normal) return button }() @@ -138,6 +151,8 @@ extension StatusAuthorView { // dateLabel dateLabel.isUserInteractionEnabled = false + + visibilityIconImageView.isUserInteractionEnabled = false } } @@ -259,47 +274,59 @@ extension StatusAuthorView { // authorPrimaryMetaContainer: H - [ authorNameLabel | (padding) | menuButton ] let authorPrimaryMetaContainer = UIStackView() authorPrimaryMetaContainer.axis = .horizontal - authorPrimaryMetaContainer.spacing = 10 + authorPrimaryMetaContainer.alignment = .center + authorPrimaryMetaContainer.spacing = 8 authorMetaContainer.addArrangedSubview(authorPrimaryMetaContainer) // authorNameLabel authorPrimaryMetaContainer.addArrangedSubview(authorNameLabel) - authorNameLabel.setContentHuggingPriority(.required - 10, for: .horizontal) - authorNameLabel.setContentCompressionResistancePriority(.required - 10, for: .horizontal) + authorNameLabel.setContentHuggingPriority(.required - 1, for: .vertical) + authorNameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + authorPrimaryMetaContainer.addArrangedSubview(UIView()) + + authorPrimaryMetaContainer.addArrangedSubview(contentSensitiveeToggleButton) + NSLayoutConstraint.activate([ + contentSensitiveeToggleButton.heightAnchor.constraint(equalToConstant: 18), + ]) + + authorPrimaryMetaContainer.setCustomSpacing(16, after: contentSensitiveeToggleButton) + // menuButton authorPrimaryMetaContainer.addArrangedSubview(menuButton) - menuButton.setContentHuggingPriority(.required - 2, for: .horizontal) - menuButton.setContentCompressionResistancePriority(.required - 2, for: .horizontal) + menuButton.setContentHuggingPriority(.required - 1, for: .horizontal) + menuButton.setContentCompressionResistancePriority(.required - 1, for: .horizontal) // authorSecondaryMetaContainer: H - [ authorUsername | usernameTrialingDotLabel | dateLabel | (padding) | contentSensitiveeToggleButton ] let authorSecondaryMetaContainer = UIStackView() authorSecondaryMetaContainer.axis = .horizontal + authorSecondaryMetaContainer.alignment = .center authorSecondaryMetaContainer.spacing = 4 authorMetaContainer.addArrangedSubview(authorSecondaryMetaContainer) authorSecondaryMetaContainer.addArrangedSubview(authorUsernameLabel) - authorUsernameLabel.setContentHuggingPriority(.required - 8, for: .horizontal) - authorUsernameLabel.setContentCompressionResistancePriority(.required - 8, for: .horizontal) + authorUsernameLabel.setContentHuggingPriority(.required - 1, for: .vertical) + authorUsernameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + authorSecondaryMetaContainer.addArrangedSubview(usernameTrialingDotLabel) - usernameTrialingDotLabel.setContentHuggingPriority(.required - 2, for: .horizontal) - usernameTrialingDotLabel.setContentCompressionResistancePriority(.required - 2, for: .horizontal) + usernameTrialingDotLabel.setContentCompressionResistancePriority(.required, for: .horizontal) + authorSecondaryMetaContainer.addArrangedSubview(dateLabel) dateLabel.setContentHuggingPriority(.required - 1, for: .horizontal) dateLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal) - authorSecondaryMetaContainer.addArrangedSubview(UIView()) - contentSensitiveeToggleButton.translatesAutoresizingMaskIntoConstraints = false - authorSecondaryMetaContainer.addArrangedSubview(contentSensitiveeToggleButton) + + authorSecondaryMetaContainer.addArrangedSubview(dateTrailingDotLabel) + dateTrailingDotLabel.setContentCompressionResistancePriority(.required, for: .horizontal) + + authorSecondaryMetaContainer.addArrangedSubview(visibilityIconImageView) NSLayoutConstraint.activate([ - contentSensitiveeToggleButton.heightAnchor.constraint(equalTo: authorUsernameLabel.heightAnchor, multiplier: 1.0).priority(.required - 1), - contentSensitiveeToggleButton.widthAnchor.constraint(equalTo: contentSensitiveeToggleButton.heightAnchor, multiplier: 1.0).priority(.required - 1), + visibilityIconImageView.heightAnchor.constraint(equalTo: authorUsernameLabel.heightAnchor), + visibilityIconImageView.widthAnchor.constraint(equalTo: visibilityIconImageView.heightAnchor), ]) - authorUsernameLabel.setContentHuggingPriority(.required - 1, for: .vertical) - authorUsernameLabel.setContentCompressionResistancePriority(.required - 1, for: .vertical) - contentSensitiveeToggleButton.setContentHuggingPriority(.defaultLow, for: .vertical) - contentSensitiveeToggleButton.setContentHuggingPriority(.defaultLow, for: .horizontal) - contentSensitiveeToggleButton.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) - contentSensitiveeToggleButton.setContentCompressionResistancePriority(.defaultLow, for: .vertical) + + authorSecondaryMetaContainer.setCustomSpacing(0, after: visibilityIconImageView) + + authorSecondaryMetaContainer.addArrangedSubview(UIView()) } func layoutReport() { diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift index f52728da1..d7d2faf48 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift @@ -292,6 +292,12 @@ extension StatusView.ViewModel { authorView.dateLabel.configure(content: PlaintextMetaContent(string: text)) } .store(in: &disposeBag) + + $visibility + .sink { visibility in + authorView.visibilityIconImageView.image = visibility.image + } + .store(in: &disposeBag) } private func bindContent(statusView: StatusView) {