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

View file

@ -16,6 +16,7 @@ public struct StatusEditorView: View {
@Environment(\.dismiss) private var dismiss
@StateObject private var viewModel: StatusEditorViewModel
@FocusState private var isSpoilerTextFocused: Bool
public init(mode: StatusEditorViewModel.Mode) {
_viewModel = StateObject(wrappedValue: .init(mode: mode))
@ -26,6 +27,7 @@ public struct StatusEditorView: View {
ZStack(alignment: .bottom) {
ScrollView {
Divider()
spoilerTextView
VStack(spacing: 12) {
accountHeaderView
.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
private var accountHeaderView: some View {
if let account = currentAccount.account {
@ -179,7 +195,7 @@ public struct StatusEditorView: View {
private var accessoryView: some View {
VStack(spacing: 0) {
Divider()
HStack(spacing: 16) {
HStack(alignment: .center, spacing: 16) {
PhotosPicker(selection: $viewModel.selectedMedias,
matching: .images) {
Image(systemName: "photo.fill.on.rectangle.fill")
@ -197,12 +213,22 @@ public struct StatusEditorView: View {
Image(systemName: "number")
}
Button {
withAnimation {
viewModel.spoilerOn.toggle()
}
isSpoilerTextFocused.toggle()
} label: {
Image(systemName: viewModel.spoilerOn ? "exclamationmark.triangle.fill": "exclamationmark.triangle")
}
visibilityMenu
Spacer()
characterCountView
}
.frame(height: 20)
.padding(.horizontal, DS.Constants.layoutPadding)
.padding(.vertical, 12)
.background(.ultraThinMaterial)
@ -227,7 +253,6 @@ public struct StatusEditorView: View {
} label: {
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 isPosting: Bool = false
@ -64,13 +67,13 @@ public class StatusEditorViewModel: ObservableObject {
postStatus = try await client.post(endpoint: Statuses.postStatus(status: statusText.string,
inReplyTo: mode.replyToStatus?.id,
mediaIds: mediasImages.compactMap{ $0.mediaAttachement?.id },
spoilerText: nil,
spoilerText: spoilerOn ? spoilerText : nil,
visibility: visibility))
case let .edit(status):
postStatus = try await client.put(endpoint: Statuses.editStatus(id: status.id,
status: statusText.string,
mediaIds: mediasImages.compactMap{ $0.mediaAttachement?.id },
spoilerText: nil,
spoilerText: spoilerOn ? spoilerText : nil,
visibility: visibility))
}
generator.notificationOccurred(.success)
@ -90,7 +93,10 @@ public class StatusEditorViewModel: ObservableObject {
selectedRange = .init(location: statusText.string.utf16.count, length: 0)
case let .edit(status):
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):
self.embededStatus = status
if let url = status.reblog?.url ?? status.url {

View file

@ -67,17 +67,17 @@ public struct StatusRowView: View {
var replyView: some View {
if let accountId = viewModel.status.inReplyToAccountId,
let mention = viewModel.status.mentions.first(where: { $0.id == accountId}) {
HStack(spacing: 2) {
Image(systemName:"arrowshape.turn.up.left.fill")
Text("Replied to")
Text(mention.username)
}
.font(.footnote)
.foregroundColor(.gray)
.fontWeight(.semibold)
.onTapGesture {
routeurPath.navigate(to: .accountDetail(id: mention.id))
}
HStack(spacing: 2) {
Image(systemName:"arrowshape.turn.up.left.fill")
Text("Replied to")
Text(mention.username)
}
.font(.footnote)
.foregroundColor(.gray)
.fontWeight(.semibold)
.onTapGesture {
routeurPath.navigate(to: .accountDetail(id: mention.id))
}
}
}
@ -96,17 +96,26 @@ public struct StatusRowView: View {
}
}
makeStatusContentView(status: status)
.padding(.vertical, viewModel.displaySpoiler ? 28 : 0)
.overlay {
if viewModel.displaySpoiler {
spoilerView
}
}
}
}
}
private func makeStatusContentView(status: AnyStatus) -> some View {
Group {
Text(status.content.asSafeAttributedString)
.font(.body)
.environment(\.openURL, OpenURLAction { url in
routeurPath.handleStatus(status: status, url: url)
})
HStack {
Text(status.content.asSafeAttributedString)
.font(.body)
.environment(\.openURL, OpenURLAction { url in
routeurPath.handleStatus(status: status, url: url)
})
Spacer()
}
if !viewModel.isEmbed, let embed = viewModel.embededStatus {
StatusEmbededView(status: embed)
@ -191,13 +200,13 @@ public struct StatusRowView: View {
} label: {
Label("Quote this post", systemImage: "quote.bubble")
}
if let url = viewModel.status.reblog?.url ?? viewModel.status.url {
Button { UIApplication.shared.open(url) } label: {
Label("View in Browser", systemImage: "safari")
}
}
if account.account?.id == viewModel.status.account.id {
Button {
routeurPath.presentedSheet = .editStatusEditor(status: viewModel.status)
@ -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 repliesCount: Int
@Published var embededStatus: Status?
@Published var displaySpoiler: Bool = false
var client: Client?
@ -33,6 +34,7 @@ public class StatusRowViewModel: ObservableObject {
self.favouritesCount = status.reblog?.favouritesCount ?? status.favouritesCount
self.reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount
self.repliesCount = status.reblog?.repliesCount ?? status.repliesCount
self.displaySpoiler = !status.spoilerText.isEmpty
}
func loadEmbededStatus() async {