Status: Spoiler support

This commit is contained in:
Thomas Ricouard 2022-12-28 10:45:05 +01:00
parent 3b8772c5da
commit 80c2086a8e
5 changed files with 95 additions and 24 deletions

View file

@ -37,6 +37,7 @@ public protocol AnyStatus {
var inReplyToAccountId: String? { get } var inReplyToAccountId: String? { get }
var visibility: Visibility { get } var visibility: Visibility { get }
var poll: Poll? { get } var poll: Poll? { get }
var spoilerText: String { get }
} }
@ -66,6 +67,7 @@ public struct Status: AnyStatus, Codable, Identifiable {
public let inReplyToAccountId: String? public let inReplyToAccountId: String?
public let visibility: Visibility public let visibility: Visibility
public let poll: Poll? public let poll: Poll?
public let spoilerText: String
public static func placeholder() -> Status { public static func placeholder() -> Status {
.init(id: UUID().uuidString, .init(id: UUID().uuidString,
@ -88,7 +90,8 @@ public struct Status: AnyStatus, Codable, Identifiable {
application: nil, application: nil,
inReplyToAccountId: nil, inReplyToAccountId: nil,
visibility: .pub, visibility: .pub,
poll: nil) poll: nil,
spoilerText: "")
} }
public static func placeholders() -> [Status] { public static func placeholders() -> [Status] {
@ -121,4 +124,5 @@ public struct ReblogStatus: AnyStatus, Codable, Identifiable {
public let inReplyToAccountId: String? public let inReplyToAccountId: String?
public let visibility: Visibility public let visibility: Visibility
public let poll: Poll? public let poll: Poll?
public let spoilerText: String
} }

View file

@ -16,6 +16,7 @@ public struct StatusEditorView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@StateObject private var viewModel: StatusEditorViewModel @StateObject private var viewModel: StatusEditorViewModel
@FocusState private var isSpoilerTextFocused: Bool
public init(mode: StatusEditorViewModel.Mode) { public init(mode: StatusEditorViewModel.Mode) {
_viewModel = StateObject(wrappedValue: .init(mode: mode)) _viewModel = StateObject(wrappedValue: .init(mode: mode))
@ -26,6 +27,7 @@ public struct StatusEditorView: View {
ZStack(alignment: .bottom) { ZStack(alignment: .bottom) {
ScrollView { ScrollView {
Divider() Divider()
spoilerTextView
VStack(spacing: 12) { VStack(spacing: 12) {
accountHeaderView accountHeaderView
.padding(.horizontal, DS.Constants.layoutPadding) .padding(.horizontal, DS.Constants.layoutPadding)
@ -80,6 +82,20 @@ public struct StatusEditorView: View {
} }
} }
@ViewBuilder
private var spoilerTextView: some View {
if viewModel.spoilerOn {
VStack {
TextField("Spoiler Text", text: $viewModel.spoilerText)
.focused($isSpoilerTextFocused)
.padding(.horizontal, DS.Constants.layoutPadding)
}
.frame(height: 35)
.background(Color.brand.opacity(0.20))
.offset(y: -8)
}
}
@ViewBuilder @ViewBuilder
private var accountHeaderView: some View { private var accountHeaderView: some View {
if let account = currentAccount.account { if let account = currentAccount.account {
@ -179,7 +195,7 @@ public struct StatusEditorView: View {
private var accessoryView: some View { private var accessoryView: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
Divider() Divider()
HStack(spacing: 16) { HStack(alignment: .center, spacing: 16) {
PhotosPicker(selection: $viewModel.selectedMedias, PhotosPicker(selection: $viewModel.selectedMedias,
matching: .images) { matching: .images) {
Image(systemName: "photo.fill.on.rectangle.fill") Image(systemName: "photo.fill.on.rectangle.fill")
@ -197,12 +213,22 @@ public struct StatusEditorView: View {
Image(systemName: "number") Image(systemName: "number")
} }
Button {
withAnimation {
viewModel.spoilerOn.toggle()
}
isSpoilerTextFocused.toggle()
} label: {
Image(systemName: viewModel.spoilerOn ? "exclamationmark.triangle.fill": "exclamationmark.triangle")
}
visibilityMenu visibilityMenu
Spacer() Spacer()
characterCountView characterCountView
} }
.frame(height: 20)
.padding(.horizontal, DS.Constants.layoutPadding) .padding(.horizontal, DS.Constants.layoutPadding)
.padding(.vertical, 12) .padding(.vertical, 12)
.background(.ultraThinMaterial) .background(.ultraThinMaterial)
@ -227,7 +253,6 @@ public struct StatusEditorView: View {
} label: { } label: {
Image(systemName: viewModel.visibility.iconName) Image(systemName: viewModel.visibility.iconName)
} }
} }
} }

View file

@ -25,6 +25,9 @@ public class StatusEditorViewModel: ObservableObject {
} }
} }
@Published var spoilerOn: Bool = false
@Published var spoilerText: String = ""
@Published var selectedRange: NSRange = .init(location: 0, length: 0) @Published var selectedRange: NSRange = .init(location: 0, length: 0)
@Published var isPosting: Bool = false @Published var isPosting: Bool = false
@ -64,13 +67,13 @@ public class StatusEditorViewModel: ObservableObject {
postStatus = try await client.post(endpoint: Statuses.postStatus(status: statusText.string, postStatus = try await client.post(endpoint: Statuses.postStatus(status: statusText.string,
inReplyTo: mode.replyToStatus?.id, inReplyTo: mode.replyToStatus?.id,
mediaIds: mediasImages.compactMap{ $0.mediaAttachement?.id }, mediaIds: mediasImages.compactMap{ $0.mediaAttachement?.id },
spoilerText: nil, spoilerText: spoilerOn ? spoilerText : nil,
visibility: visibility)) visibility: visibility))
case let .edit(status): case let .edit(status):
postStatus = try await client.put(endpoint: Statuses.editStatus(id: status.id, postStatus = try await client.put(endpoint: Statuses.editStatus(id: status.id,
status: statusText.string, status: statusText.string,
mediaIds: mediasImages.compactMap{ $0.mediaAttachement?.id }, mediaIds: mediasImages.compactMap{ $0.mediaAttachement?.id },
spoilerText: nil, spoilerText: spoilerOn ? spoilerText : nil,
visibility: visibility)) visibility: visibility))
} }
generator.notificationOccurred(.success) generator.notificationOccurred(.success)
@ -90,7 +93,10 @@ public class StatusEditorViewModel: ObservableObject {
selectedRange = .init(location: statusText.string.utf16.count, length: 0) selectedRange = .init(location: statusText.string.utf16.count, length: 0)
case let .edit(status): case let .edit(status):
statusText = .init(status.content.asSafeAttributedString) statusText = .init(status.content.asSafeAttributedString)
selectedRange = .init(location: 0, length: 0) selectedRange = .init(location: statusText.string.utf16.count, length: 0)
spoilerOn = !status.spoilerText.isEmpty
spoilerText = status.spoilerText
mediasImages = status.mediaAttachments.map{ .init(image: nil, mediaAttachement: $0, error: nil )}
case let .quote(status): case let .quote(status):
self.embededStatus = status self.embededStatus = status
if let url = status.reblog?.url ?? status.url { if let url = status.reblog?.url ?? status.url {

View file

@ -96,17 +96,26 @@ public struct StatusRowView: View {
} }
} }
makeStatusContentView(status: status) makeStatusContentView(status: status)
.padding(.vertical, viewModel.displaySpoiler ? 28 : 0)
.overlay {
if viewModel.displaySpoiler {
spoilerView
}
}
} }
} }
} }
private func makeStatusContentView(status: AnyStatus) -> some View { private func makeStatusContentView(status: AnyStatus) -> some View {
Group { Group {
HStack {
Text(status.content.asSafeAttributedString) Text(status.content.asSafeAttributedString)
.font(.body) .font(.body)
.environment(\.openURL, OpenURLAction { url in .environment(\.openURL, OpenURLAction { url in
routeurPath.handleStatus(status: status, url: url) routeurPath.handleStatus(status: status, url: url)
}) })
Spacer()
}
if !viewModel.isEmbed, let embed = viewModel.embededStatus { if !viewModel.isEmbed, let embed = viewModel.embededStatus {
StatusEmbededView(status: embed) StatusEmbededView(status: embed)
@ -209,4 +218,29 @@ public struct StatusRowView: View {
} }
} }
} }
private var spoilerView: some View {
HStack {
Spacer()
VStack {
Spacer()
Text(viewModel.status.reblog?.spoilerText ?? viewModel.status.spoilerText)
.font(.callout)
Button {
withAnimation {
viewModel.displaySpoiler = false
}
} label: {
Text("See more")
}
.buttonStyle(.bordered)
Spacer()
}
Spacer()
}
.background(.ultraThinMaterial)
.transition(.opacity)
.cornerRadius(4)
}
} }

View file

@ -14,6 +14,7 @@ public class StatusRowViewModel: ObservableObject {
@Published var reblogsCount: Int @Published var reblogsCount: Int
@Published var repliesCount: Int @Published var repliesCount: Int
@Published var embededStatus: Status? @Published var embededStatus: Status?
@Published var displaySpoiler: Bool = false
var client: Client? var client: Client?
@ -33,6 +34,7 @@ public class StatusRowViewModel: ObservableObject {
self.favouritesCount = status.reblog?.favouritesCount ?? status.favouritesCount self.favouritesCount = status.reblog?.favouritesCount ?? status.favouritesCount
self.reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount self.reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount
self.repliesCount = status.reblog?.repliesCount ?? status.repliesCount self.repliesCount = status.reblog?.repliesCount ?? status.repliesCount
self.displaySpoiler = !status.spoilerText.isEmpty
} }
func loadEmbededStatus() async { func loadEmbededStatus() async {