mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-12-22 23:26:37 +00:00
Status: Spoiler support
This commit is contained in:
parent
3b8772c5da
commit
80c2086a8e
5 changed files with 95 additions and 24 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue