mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 20:29:53 +00:00
ios: reports inline (#5466)
* initial types
* changes types
* decode
* possible mock for inline report
* remove avatar
* diff
* updates
* parser and display message
* send messages and support placeholder
* profile reports and all reports working
* new api
* check member support for receiving reports
* report chat item text
* moderator role
* placeholder on text compose for report
* rename method
* remove need to have reported item in memory to action
* archived reports
* changes/fix
* fix block member
* delete and moderate
* archive
* report reason
* context menu/moderation fixes
* typo
* not needed
* report reason as caption, and change text
* remove auto archive
* move placeholder to match text
* prefix red italic report
* archive
* apply mark deleted fix
* Revert "apply mark deleted fix"
This reverts commit b12f14c0f5
.
* remove extra space
* context menu rework
* strings, icons
* recheck items extra check on reports
* simplify
* simpler
* reports: never show for own messages, disable attachments, hide when recording or live
* style, allow local deletion
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
parent
8dc29082d5
commit
7e344b3ee8
17 changed files with 617 additions and 99 deletions
|
@ -24,6 +24,7 @@ enum ComposeContextItem {
|
|||
case quotedItem(chatItem: ChatItem)
|
||||
case editingItem(chatItem: ChatItem)
|
||||
case forwardingItems(chatItems: [ChatItem], fromChatInfo: ChatInfo)
|
||||
case reportedItem(chatItem: ChatItem, reason: ReportReason)
|
||||
}
|
||||
|
||||
enum VoiceMessageRecordingState {
|
||||
|
@ -116,13 +117,31 @@ struct ComposeState {
|
|||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var reporting: Bool {
|
||||
switch contextItem {
|
||||
case .reportedItem: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
var submittingValidReport: Bool {
|
||||
switch contextItem {
|
||||
case let .reportedItem(_, reason):
|
||||
switch reason {
|
||||
case .other: return !message.isEmpty
|
||||
default: return true
|
||||
}
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
var sendEnabled: Bool {
|
||||
switch preview {
|
||||
case let .mediaPreviews(media): return !media.isEmpty
|
||||
case .voicePreview: return voiceMessageRecordingState == .finished
|
||||
case .filePreview: return true
|
||||
default: return !message.isEmpty || forwarding || liveMessage != nil
|
||||
default: return !message.isEmpty || forwarding || liveMessage != nil || submittingValidReport
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,7 +194,7 @@ struct ComposeState {
|
|||
}
|
||||
|
||||
var attachmentDisabled: Bool {
|
||||
if editing || forwarding || liveMessage != nil || inProgress { return true }
|
||||
if editing || forwarding || liveMessage != nil || inProgress || reporting { return true }
|
||||
switch preview {
|
||||
case .noPreview: return false
|
||||
case .linkPreview: return false
|
||||
|
@ -193,6 +212,15 @@ struct ComposeState {
|
|||
}
|
||||
}
|
||||
|
||||
var placeholder: String? {
|
||||
switch contextItem {
|
||||
case let .reportedItem(_, reason):
|
||||
return reason.text
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var empty: Bool {
|
||||
message == "" && noPreview
|
||||
}
|
||||
|
@ -297,6 +325,11 @@ struct ComposeView: View {
|
|||
ContextInvitingContactMemberView()
|
||||
Divider()
|
||||
}
|
||||
|
||||
if case let .reportedItem(_, reason) = composeState.contextItem {
|
||||
reportReasonView(reason)
|
||||
Divider()
|
||||
}
|
||||
// preference checks should match checks in forwarding list
|
||||
let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks)
|
||||
let fileProhibited = composeState.attachmentPreview && !chat.groupFeatureEnabled(.files)
|
||||
|
@ -686,6 +719,27 @@ struct ComposeView: View {
|
|||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(.thinMaterial)
|
||||
}
|
||||
|
||||
|
||||
private func reportReasonView(_ reason: ReportReason) -> some View {
|
||||
let reportText = switch reason {
|
||||
case .spam: NSLocalizedString("Report spam: only group moderators will see it.", comment: "report reason")
|
||||
case .profile: NSLocalizedString("Report member profile: only group moderators will see it.", comment: "report reason")
|
||||
case .community: NSLocalizedString("Report violation: only group moderators will see it.", comment: "report reason")
|
||||
case .illegal: NSLocalizedString("Report content: only group moderators will see it.", comment: "report reason")
|
||||
case .other: NSLocalizedString("Report other: only group moderators will see it.", comment: "report reason")
|
||||
case .unknown: "" // Should never happen
|
||||
}
|
||||
|
||||
return Text(reportText)
|
||||
.italic()
|
||||
.font(.caption)
|
||||
.padding(12)
|
||||
.frame(minHeight: 44)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(.thinMaterial)
|
||||
}
|
||||
|
||||
|
||||
@ViewBuilder private func contextItemView() -> some View {
|
||||
switch composeState.contextItem {
|
||||
|
@ -715,6 +769,15 @@ struct ComposeView: View {
|
|||
cancelContextItem: { composeState = composeState.copy(contextItem: .noContextItem) }
|
||||
)
|
||||
Divider()
|
||||
case let .reportedItem(chatItem: reportedItem, _):
|
||||
ContextItemView(
|
||||
chat: chat,
|
||||
contextItems: [reportedItem],
|
||||
contextIcon: "flag",
|
||||
cancelContextItem: { composeState = composeState.copy(contextItem: .noContextItem) },
|
||||
contextIconForeground: Color.red
|
||||
)
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -746,6 +809,8 @@ struct ComposeView: View {
|
|||
sent = await updateMessage(ci, live: live)
|
||||
} else if let liveMessage = liveMessage, liveMessage.sentMsg != nil {
|
||||
sent = await updateMessage(liveMessage.chatItem, live: live)
|
||||
} else if case let .reportedItem(chatItem, reason) = composeState.contextItem {
|
||||
sent = await send(reason, chatItemId: chatItem.id)
|
||||
} else {
|
||||
var quoted: Int64? = nil
|
||||
if case let .quotedItem(chatItem: quotedItem) = composeState.contextItem {
|
||||
|
@ -872,6 +937,8 @@ struct ComposeView: View {
|
|||
return .voice(text: msgText, duration: duration)
|
||||
case .file:
|
||||
return .file(msgText)
|
||||
case .report(_, let reason):
|
||||
return .report(text: msgText, reason: reason)
|
||||
case .unknown(let type, _):
|
||||
return .unknown(type: type, text: msgText)
|
||||
}
|
||||
|
@ -891,7 +958,25 @@ struct ComposeView: View {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func send(_ reportReason: ReportReason, chatItemId: Int64) async -> ChatItem? {
|
||||
if let chatItems = await apiReportMessage(
|
||||
groupId: chat.chatInfo.apiId,
|
||||
chatItemId: chatItemId,
|
||||
reportReason: reportReason,
|
||||
reportText: msgText
|
||||
) {
|
||||
await MainActor.run {
|
||||
for chatItem in chatItems {
|
||||
chatModel.addChatItem(chat.chatInfo, chatItem)
|
||||
}
|
||||
}
|
||||
return chatItems.first
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func send(_ mc: MsgContent, quoted: Int64?, file: CryptoFile? = nil, live: Bool = false, ttl: Int?) async -> ChatItem? {
|
||||
await send(
|
||||
[ComposedMessage(fileSource: file, quotedItemId: quoted, msgContent: mc)],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue