Report a post

This commit is contained in:
Lumaa 2024-08-10 02:40:41 +02:00
parent 861c57566e
commit 767838e734
7 changed files with 475 additions and 95 deletions

View File

@ -49,6 +49,7 @@
B999DE5C2B76F8CB00509868 /* ContactsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B999DE5B2B76F8CB00509868 /* ContactsView.swift */; };
B999DE5E2B76F9D100509868 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = B999DE5D2B76F9D100509868 /* Message.swift */; };
B999DE602B76FB3E00509868 /* ContactRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B999DE5F2B76FB3E00509868 /* ContactRow.swift */; };
B9A80DDA2C66DE1000DE3D88 /* ReportStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A80DD92C66DE1000DE3D88 /* ReportStatusView.swift */; };
B9A8DABA2BB7364300A890CC /* PostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A8DAB92BB7364300A890CC /* PostsView.swift */; };
B9B469B02B9A275F00AD5585 /* FollowGoalWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469AF2B9A275F00AD5585 /* FollowGoalWidget.swift */; };
B9B469B22B9A6E8300AD5585 /* PrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469B12B9A6E8300AD5585 /* PrivacyView.swift */; };
@ -226,6 +227,7 @@
B999DE5B2B76F8CB00509868 /* ContactsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsView.swift; sourceTree = "<group>"; };
B999DE5D2B76F9D100509868 /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = "<group>"; };
B999DE5F2B76FB3E00509868 /* ContactRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRow.swift; sourceTree = "<group>"; };
B9A80DD92C66DE1000DE3D88 /* ReportStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusView.swift; sourceTree = "<group>"; };
B9A8DAB92BB7364300A890CC /* PostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostsView.swift; sourceTree = "<group>"; };
B9B469AF2B9A275F00AD5585 /* FollowGoalWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowGoalWidget.swift; sourceTree = "<group>"; };
B9B469B12B9A6E8300AD5585 /* PrivacyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyView.swift; sourceTree = "<group>"; };
@ -540,6 +542,7 @@
B93B677B2B433A6E000892E9 /* PostingView.swift */,
B9F8FA152B5D3AC30044DAB4 /* SafariView.swift */,
B9A8DAB92BB7364300A890CC /* PostsView.swift */,
B9A80DD92C66DE1000DE3D88 /* ReportStatusView.swift */,
);
path = Views;
sourceTree = "<group>";
@ -910,6 +913,7 @@
B9EBE8562B47256900FB594D /* PostAttachment.swift in Sources */,
B9EBE8582B474FD600FB594D /* AppDelegate.swift in Sources */,
B9FD18982C55108F00A74A71 /* EditProfileView.swift in Sources */,
B9A80DDA2C66DE1000DE3D88 /* ReportStatusView.swift in Sources */,
B93B677C2B433A6E000892E9 /* PostingView.swift in Sources */,
B97BCE262B3DE5A10044756D /* AccountView.swift in Sources */,
B98627312B86F23500844245 /* LoggedAccounts.swift in Sources */,

View File

@ -36,6 +36,10 @@ struct CompactPostView: View {
}
}
.withCovers(sheetDestination: $navigator.presentedCover)
.containerShape(Rectangle())
.contextMenu {
PostMenu(status: status)
}
.onAppear {
do {
preferences = try UserPreferences.loadAsCurrent() ?? UserPreferences.defaultPreferences
@ -136,27 +140,7 @@ struct CompactPostView: View {
PostCardView(card: status.card!)
}
if !status.mediaAttachments.isEmpty {
if status.mediaAttachments.count > 1 {
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .firstTextBaseline, spacing: 5) {
ForEach(status.mediaAttachments) { attachment in
PostAttachment(attachment: attachment, isFeatured: false, isImaging: imaging)
.blur(radius: status.sensitive ? 15.0 : 0)
.onTapGesture {
navigator.presentedCover = .media(attachments: status.mediaAttachments, selected: attachment)
}
}
}
}
.scrollClipDisabled()
} else {
PostAttachment(attachment: status.mediaAttachments.first!, isImaging: imaging)
.onTapGesture {
navigator.presentedCover = .media(attachments: status.mediaAttachments, selected: status.mediaAttachments[0])
}
}
}
attachmnts
// }
if hasQuote && !quoted {
@ -183,7 +167,32 @@ struct CompactPostView: View {
// }
}
}
@ViewBuilder
var attachmnts: some View {
if !status.mediaAttachments.isEmpty {
if status.mediaAttachments.count > 1 {
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .firstTextBaseline, spacing: 5) {
ForEach(status.mediaAttachments) { attachment in
PostAttachment(attachment: attachment, isFeatured: false, isImaging: imaging)
.blur(radius: status.sensitive ? 15.0 : 0)
.onTapGesture {
navigator.presentedCover = .media(attachments: status.mediaAttachments, selected: attachment)
}
}
}
}
.scrollClipDisabled()
} else {
PostAttachment(attachment: status.mediaAttachments.first!, isImaging: imaging)
.onTapGesture {
navigator.presentedCover = .media(attachments: status.mediaAttachments, selected: status.mediaAttachments[0])
}
}
}
}
var notices: some View {
ZStack {
if pinned {

View File

@ -22,80 +22,80 @@ struct PostMenu: View {
}
return false
}
var body: some View {
Menu {
if isOwner {
Button(role: .destructive) {
Task {
await deleteStatus()
}
} label: {
Label("status.menu.delete", systemImage: "trash")
}
Button {
navigator.presentedSheet = .post(content: status.reblogAsAsStatus?.content.asRawText ?? status.content.asRawText, replyId: nil, editId: status.reblogAsAsStatus?.id ?? status.id)
} label: {
Label("status.menu.edit", systemImage: "pencil.and.scribble")
}
Divider()
Menu {
Button {
openURL(URL(string: AltClients.IvoryApp.createPost(status.reblogAsAsStatus?.content.asRawText ?? status.content.asRawText))!)
} label: {
Text(AltClients.IvoryApp.name)
}
Button {
openURL(URL(string: AltClients.ThreadsApp.createPost(status.reblogAsAsStatus?.content.asRawText ?? status.content.asRawText))!)
} label: {
Text(AltClients.ThreadsApp.name)
}
Button {
openURL(URL(string: AltClients.XApp.createPost(status.reblogAsAsStatus?.content.asRawText ?? status.content.asRawText))!)
} label: {
Text(AltClients.XApp.name)
}
} label: {
Label("status.cross-post.alts", systemImage: "shuffle")
}
Divider()
}
Menu {
ShareLink(item: URL(string: status.url ?? "https://joinmastodon.org/")!) {
Label("status.menu.share-link", systemImage: "square.and.arrow.up")
}
Button {
Task {
createImage()
}
} label: {
Label("status.menu.share-image", systemImage: "photo")
}
Divider()
Button {
UIPasteboard.general.setValue(status.reblogAsAsStatus?.content.asRawText ?? status.content.asRawText, forPasteboardType: UTType.plainText.identifier)
} label: {
Label("status.menu.copy-text", systemImage: "list.clipboard")
if isOwner {
Button(role: .destructive) {
Task {
await deleteStatus()
}
} label: {
Label("status.menu.share", systemImage: "paperplane")
Label("status.menu.delete", systemImage: "trash")
}
Button {
navigator.presentedSheet = .post(content: status.reblogAsAsStatus?.content.asRawText ?? status.content.asRawText, replyId: nil, editId: status.reblogAsAsStatus?.id ?? status.id)
} label: {
Label("status.menu.edit", systemImage: "pencil.and.scribble")
}
Divider()
Menu {
Button {
openURL(URL(string: AltClients.IvoryApp.createPost(status.reblogAsAsStatus?.content.asRawText ?? status.content.asRawText))!)
} label: {
Text(AltClients.IvoryApp.name)
}
Button {
openURL(URL(string: AltClients.ThreadsApp.createPost(status.reblogAsAsStatus?.content.asRawText ?? status.content.asRawText))!)
} label: {
Text(AltClients.ThreadsApp.name)
}
Button {
openURL(URL(string: AltClients.XApp.createPost(status.reblogAsAsStatus?.content.asRawText ?? status.content.asRawText))!)
} label: {
Text(AltClients.XApp.name)
}
} label: {
Label("status.cross-post.alts", systemImage: "shuffle")
}
Divider()
}
Menu {
ShareLink(item: URL(string: status.url ?? "https://joinmastodon.org/")!) {
Label("status.menu.share-link", systemImage: "square.and.arrow.up")
}
Button {
Task {
createImage()
}
} label: {
Label("status.menu.share-image", systemImage: "photo")
}
Divider()
Button {
UIPasteboard.general.setValue(status.reblogAsAsStatus?.content.asRawText ?? status.content.asRawText, forPasteboardType: UTType.plainText.identifier)
} label: {
Label("status.menu.copy-text", systemImage: "list.clipboard")
}
} label: {
Image(systemName: "ellipsis")
.foregroundStyle(Color.white.opacity(0.3))
.font(.body)
.contentShape(Rectangle())
.padding(7.5)
Label("status.menu.share", systemImage: "paperplane")
}
Divider()
Button(role: .destructive) {
navigator.presentedSheet = .reportStatus(status: status)
} label: {
Label("status.menu.report", systemImage: "exclamationmark.triangle.fill")
}
}

View File

@ -107,7 +107,10 @@ public enum SheetDestination: Identifiable {
case shareImage(image: UIImage, status: Status)
case update
case filter
case reportStatus(status: Status)
// case reportUser
public var id: String {
switch self {
case .welcome:
@ -131,6 +134,9 @@ public enum SheetDestination: Identifiable {
return "update"
case .filter:
return "contentfilter"
case .reportStatus:
return "reportStatus"
}
}
@ -157,6 +163,9 @@ public enum SheetDestination: Identifiable {
return false
case .filter:
return false
case .reportStatus:
return false
}
}
}
@ -260,6 +269,8 @@ extension View {
ShareSheet(image: image, status: status)
case .update:
UpdateView()
case let .reportStatus(status):
ReportStatusView(status: status)
default:
EmptySheetView(destId: destination.id)
}

View File

@ -1461,6 +1461,70 @@
}
}
},
"general.report.confirm" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Send the report?"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Envoyer ce signalement ?"
}
}
}
},
"general.report.confirm.cancel" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cancel"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Annuler"
}
}
}
},
"general.report.confirm.message" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "If you falsely report a post or an account, your own account could face consequences. Remember to report only content that is against the rules."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Si vous faites un faux signalement, votre compte pourrait subir des conséquences. Rappelez-vous de signaler seulement le contenu qui est contre les règles."
}
}
}
},
"general.report.confirm.ok" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "OK"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "OK"
}
}
}
},
"instance.rules" : {
"localizations" : {
"en" : {
@ -3168,6 +3232,22 @@
}
}
},
"status.menu.report" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Report"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Signaler"
}
}
}
},
"status.menu.share" : {
"localizations" : {
"en" : {
@ -3644,6 +3724,102 @@
}
}
},
"status.report" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Report"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Signaler"
}
}
}
},
"status.report.cancel" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cancel"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Annuler"
}
}
}
},
"status.report.comment" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Describe the issue with this post"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Commentez les soucis de cette publication"
}
}
}
},
"status.report.preview" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Selected Post"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Post sélectionné"
}
}
}
},
"status.report.preview.footer" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "This preview does not contain polls, URL previews, the authors profile picture."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cette prévisualisation ne contient pas les sondages, prévisualisations dURLs, la photo de profil de lauteur"
}
}
}
},
"status.report.title" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Report a post"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Signaler une publication"
}
}
}
},
"status.reposted-by.%@" : {
"localizations" : {
"en" : {
@ -4218,4 +4394,4 @@
}
},
"version" : "1.0"
}
}

View File

@ -73,8 +73,16 @@ struct PostDetailsView: View {
}
Spacer()
PostMenu(status: status)
Menu {
PostMenu(status: status)
} label: {
Image(systemName: "ellipsis")
.foregroundStyle(Color.white.opacity(0.3))
.font(.body)
.contentShape(Rectangle())
.padding(7.5)
}
.padding([.trailing, .top])
}

View File

@ -0,0 +1,172 @@
// Made by Lumaa
import SwiftUI
struct ReportStatusView: View {
@Environment(AccountManager.self) private var accountManager: AccountManager
@Environment(\.dismiss) private var dismiss: DismissAction
var status: Status
@State private var comment: String = ""
@State private var confirmationAlert: Bool = false
var body: some View {
NavigationStack {
Form {
Section {
TextField("status.report.comment", text: $comment, axis: .vertical)
.frame(maxHeight: 300)
Button {
confirmationAlert.toggle()
} label: {
Text("status.report")
.foregroundStyle(Color.red)
.bold()
}
}
Section(header: Text("status.report.preview"), footer: Text("status.report.preview.footer")) {
StatusPreview(status: status)
}
}
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button {
dismiss()
} label: {
Text("status.report.cancel")
}
}
// ToolbarItem(placement: .confirmationAction) {
// Button {
// confirmationAlert.toggle()
// } label: {
// Text("status.report")
// .foregroundStyle(Color.red)
// }
// }
}
.alert(
"general.report.confirm",
isPresented: $confirmationAlert,
actions: {
Button(role: .destructive) {
Task {
if comment.isEmpty {
await reportStatus()
} else {
await reportStatus(comment: comment)
}
HapticManager.playHaptics(haptics: Haptic.success)
dismiss()
}
} label: {
Text("general.report.confirm.ok")
}
Button(role: .cancel) {} label: {
Text("general.report.confirm.cancel")
}
},
message: { Text("general.report.confirm.message") }
)
.navigationTitle(Text("status.report.title"))
.navigationBarTitleDisplayMode(.large)
}
}
private func reportStatus(comment: String = "*No information was given*") async {
if let client = accountManager.getClient() {
_ = try? await client
.post(
endpoint: Statuses
.report(
accountId: status.account.id,
statusId: status.id,
comment: comment
)
)
}
}
struct StatusPreview: View {
var status: Status
var body: some View {
HStack(alignment: .top, spacing: 0) {
VStack(alignment: .leading) {
// MARK: Status main content
VStack(alignment: .leading, spacing: 10) {
VStack(alignment: .leading, spacing: 2) {
Text("@\(status.account.acct)")
.font(.callout)
.multilineTextAlignment(.leading)
.bold()
if status.inReplyToAccountId != nil {
if let user = status.mentions.first(where: { $0.id == status.inReplyToAccountId }) {
Text("status.replied-to.\(user.username)")
.multilineTextAlignment(.leading)
.lineLimit(1)
.font(.caption)
.foregroundStyle(Color(uiColor: UIColor.label).opacity(0.3))
}
}
}
if !status.content.asRawText.isEmpty {
TextEmoji(status.content, emojis: status.emojis, language: status.language)
.multilineTextAlignment(.leading)
.frame(width: 300, alignment: .topLeading)
.fixedSize(horizontal: false, vertical: true)
.font(.callout)
.contentShape(Rectangle())
}
attachmnts
}
}
}
}
@ViewBuilder
var attachmnts: some View {
if !status.mediaAttachments.isEmpty {
if status.mediaAttachments.count > 1 {
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .firstTextBaseline, spacing: 5) {
ForEach(status.mediaAttachments) { attachment in
PostAttachment(attachment: attachment, isFeatured: false)
.blur(radius: status.sensitive ? 15.0 : 0)
}
}
}
.scrollClipDisabled()
} else {
PostAttachment(attachment: status.mediaAttachments.first!)
}
}
}
}
}
#Preview("FR") {
ReportStatusView(status: .placeholder())
.environment(AccountManager())
.environment(\.locale, Locale(identifier: "fr-fr"))
}
#Preview("Sheet") {
ZStack {
Text(String("Hello world!"))
}
.interactiveDismissDisabled()
.presentationDragIndicator(.hidden)
.sheet(isPresented: .constant(true)) {
ReportStatusView(status: .placeholder())
.environment(AccountManager())
}
}