ios: UI to moderate messages of other members (#1980)

* ios: UI to moderate messages of other members

* split moderate action

* do not show moderate button on moderated, show alert
This commit is contained in:
Evgeny Poberezkin
2023-03-06 21:57:58 +00:00
committed by GitHub
parent 8f0e7512be
commit f1a9814faa
6 changed files with 86 additions and 14 deletions
+6
View File
@@ -288,6 +288,12 @@ func apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteM
throw r
}
func apiDeleteMemberChatItem(groupId: Int64, groupMemberId: Int64, itemId: Int64) async throws -> (ChatItem, ChatItem?) {
let r = await chatSendCmd(.apiDeleteMemberChatItem(groupId: groupId, groupMemberId: groupMemberId, itemId: itemId), bgDelay: msgDelay)
if case let .chatItemDeleted(_, deletedChatItem, toChatItem, _) = r { return (deletedChatItem.chatItem, toChatItem?.chatItem) }
throw r
}
func apiGetNtfToken() -> (DeviceToken?, NtfTknStatus?, NotificationsMode) {
let r = chatSendCmdSync(.apiGetNtfToken)
switch r {
@@ -29,8 +29,12 @@ struct FramedItemView: View {
var body: some View {
let v = ZStack(alignment: .bottomTrailing) {
VStack(alignment: .leading, spacing: 0) {
if chatItem.meta.itemDeleted != nil {
framedItemHeader(icon: "trash", caption: Text("marked deleted").italic())
if let di = chatItem.meta.itemDeleted {
if case let .moderated(byGroupMember) = di {
framedItemHeader(icon: "flag", caption: Text("moderated by \(byGroupMember.chatViewName)").italic())
} else {
framedItemHeader(icon: "trash", caption: Text("marked deleted").italic())
}
} else if chatItem.meta.isLive {
framedItemHeader(caption: Text("LIVE"))
}
@@ -19,10 +19,11 @@ struct MarkedDeletedItemView: View {
if showMember, let member = chatItem.memberDisplayName {
Text(member).font(.caption).fontWeight(.medium) + Text(": ").font(.caption)
}
Text("marked deleted")
.font(.caption)
.foregroundColor(.secondary)
.italic()
if case let .moderated(byGroupMember) = chatItem.meta.itemDeleted {
markedDeletedText("moderated by \(byGroupMember.chatViewName)")
} else {
markedDeletedText("marked deleted")
}
CIMetaView(chatItem: chatItem)
.padding(.horizontal, 12)
}
@@ -32,6 +33,14 @@ struct MarkedDeletedItemView: View {
.cornerRadius(18)
.textSelection(.disabled)
}
func markedDeletedText(_ s: LocalizedStringKey) -> some View {
Text(s)
.font(.caption)
.foregroundColor(.secondary)
.italic()
.lineLimit(1)
}
}
struct MarkedDeletedItemView_Previews: PreviewProvider {
+45 -8
View File
@@ -492,8 +492,13 @@ struct ChatView: View {
if !live || !ci.meta.isLive {
menu.append(deleteUIAction())
}
if let (groupInfo, _) = ci.memberToModerate(chat.chatInfo) {
menu.append(moderateUIAction(groupInfo))
}
} else if ci.meta.itemDeleted != nil {
menu.append(revealUIAction())
if !ci.isDeletedContent {
menu.append(revealUIAction())
}
menu.append(deleteUIAction())
} else if ci.isDeletedContent {
menu.append(deleteUIAction())
@@ -595,7 +600,29 @@ struct ChatView: View {
deletingItem = ci
}
}
private func moderateUIAction(_ groupInfo: GroupInfo) -> UIAction {
UIAction(
title: NSLocalizedString("Moderate", comment: "chat item action"),
image: UIImage(systemName: "flag"),
attributes: [.destructive]
) { _ in
AlertManager.shared.showAlert(Alert(
title: Text("Delete member message?"),
message: Text(
groupInfo.fullGroupPreferences.fullDelete.on
? "The message will be deleted for all members."
: "The message will be marked as moderated for all members."
),
primaryButton: .destructive(Text("Delete")) {
deletingItem = ci
deleteMessage(.cidmBroadcast)
},
secondaryButton: .cancel()
))
}
}
private func revealUIAction() -> UIAction {
UIAction(
title: NSLocalizedString("Reveal", comment: "chat item action"),
@@ -638,12 +665,22 @@ struct ChatView: View {
logger.debug("ChatView deleteMessage: in Task")
do {
if let di = deletingItem {
let (deletedItem, toItem) = try await apiDeleteChatItem(
type: chat.chatInfo.chatType,
id: chat.chatInfo.apiId,
itemId: di.id,
mode: mode
)
var deletedItem: ChatItem
var toItem: ChatItem?
if let (groupInfo, groupMember) = di.memberToModerate(chat.chatInfo) {
(deletedItem, toItem) = try await apiDeleteMemberChatItem(
groupId: groupInfo.apiId,
groupMemberId: groupMember.groupMemberId,
itemId: di.id
)
} else {
(deletedItem, toItem) = try await apiDeleteChatItem(
type: chat.chatInfo.chatType,
id: chat.chatInfo.apiId,
itemId: di.id,
mode: mode
)
}
DispatchQueue.main.async {
deletingItem = nil
if let toItem = toItem {
+3
View File
@@ -33,6 +33,7 @@ public enum ChatCommand {
case apiSendMessage(type: ChatType, id: Int64, file: String?, quotedItemId: Int64?, msg: MsgContent, live: Bool)
case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent, live: Bool)
case apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode)
case apiDeleteMemberChatItem(groupId: Int64, groupMemberId: Int64, itemId: Int64)
case apiGetNtfToken
case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode)
case apiVerifyToken(token: DeviceToken, nonce: String, code: String)
@@ -122,6 +123,7 @@ public enum ChatCommand {
return "/_send \(ref(type, id)) live=\(onOff(live)) json \(msg)"
case let .apiUpdateChatItem(type, id, itemId, mc, live): return "/_update item \(ref(type, id)) \(itemId) live=\(onOff(live)) \(mc.cmdString)"
case let .apiDeleteChatItem(type, id, itemId, mode): return "/_delete item \(ref(type, id)) \(itemId) \(mode.rawValue)"
case let .apiDeleteMemberChatItem(groupId, groupMemberId, itemId): return "/_delete member item #\(groupId) \(groupMemberId) \(itemId)"
case .apiGetNtfToken: return "/_ntf get "
case let .apiRegisterToken(token, notificationMode): return "/_ntf register \(token.cmdString) \(notificationMode.rawValue)"
case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)"
@@ -216,6 +218,7 @@ public enum ChatCommand {
case .apiSendMessage: return "apiSendMessage"
case .apiUpdateChatItem: return "apiUpdateChatItem"
case .apiDeleteChatItem: return "apiDeleteChatItem"
case .apiDeleteMemberChatItem: return "apiDeleteMemberChatItem"
case .apiGetNtfToken: return "apiGetNtfToken"
case .apiRegisterToken: return "apiRegisterToken"
case .apiVerifyToken: return "apiVerifyToken"
+13
View File
@@ -1722,6 +1722,8 @@ public struct ChatItem: Identifiable, Decodable {
switch content {
case .sndDeleted: return true
case .rcvDeleted: return true
case .sndModerated: return true
case .rcvModerated: return true
default: return false
}
}
@@ -1789,6 +1791,17 @@ public struct ChatItem: Identifiable, Decodable {
}
}
public func memberToModerate(_ chatInfo: ChatInfo) -> (GroupInfo, GroupMember)? {
switch (chatInfo, chatDir) {
case let (.group(groupInfo), .groupRcv(groupMember)):
let m = groupInfo.membership
return m.memberRole >= .admin && m.memberRole >= groupMember.memberRole && meta.itemDeleted == nil
? (groupInfo, groupMember)
: nil
default: return nil
}
}
public static func getSample (_ id: Int64, _ dir: CIDirection, _ ts: Date, _ text: String, _ status: CIStatus = .sndNew, quotedItem: CIQuote? = nil, file: CIFile? = nil, itemDeleted: CIDeleted? = nil, itemEdited: Bool = false, itemLive: Bool = false, editable: Bool = true) -> ChatItem {
ChatItem(
chatDir: dir,