ios: members connected aggregated item
This commit is contained in:
parent
1bc880877d
commit
08ad0ecf21
@ -491,6 +491,14 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func getNextChatItem(_ ci: ChatItem) -> ChatItem? {
|
||||
if let i = getChatItemIndex(ci), i - 1 >= 0 {
|
||||
return reversedChatItems[i - 1]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func popChat(_ id: String) {
|
||||
if let i = getChatIndex(id) {
|
||||
popChat_(i)
|
||||
|
@ -0,0 +1,51 @@
|
||||
//
|
||||
// CIMembersConnectedView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by spaced4ndy on 11.08.2023.
|
||||
// Copyright © 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct CIMembersConnectedView: View {
|
||||
@EnvironmentObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
var members: [GroupMember]
|
||||
@State private var selectedMember: GroupMember? = nil
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
ForEach(members, id: \.groupMemberId) { member in
|
||||
memberPicture(member)
|
||||
}
|
||||
}
|
||||
chatEventText(chatItem)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder func memberPicture(_ member: GroupMember) -> some View {
|
||||
let v = ProfileImage(imageStr: member.memberProfile.image)
|
||||
.frame(width: memberImageSize, height: memberImageSize)
|
||||
if case let .group(groupInfo) = chat.chatInfo {
|
||||
v
|
||||
.onTapGesture { selectedMember = member }
|
||||
.appSheet(item: $selectedMember) { member in
|
||||
GroupMemberInfoView(groupInfo: groupInfo, member: member, navigation: true)
|
||||
}
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CIMembersConnectedView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CIMembersConnectedView(
|
||||
chatItem: ChatItem.getMembersConnectedSample(),
|
||||
members: [GroupMember.sampleData, GroupMember.sampleData, GroupMember.sampleData]
|
||||
)
|
||||
}
|
||||
}
|
@ -92,6 +92,7 @@ struct ChatItemContentView<Content: View>: View {
|
||||
case .sndModerated: deletedItemView()
|
||||
case .rcvModerated: deletedItemView()
|
||||
case let .invalidJSON(json): CIInvalidJSONView(json: json)
|
||||
case let .membersConnected(members): CIMembersConnectedView(chatItem: chatItem, members: members)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
import SwiftyGif
|
||||
|
||||
private let memberImageSize: CGFloat = 34
|
||||
let memberImageSize: CGFloat = 34
|
||||
|
||||
struct ChatView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@ -430,35 +430,46 @@ struct ChatView: View {
|
||||
@ViewBuilder private func chatItemView(_ ci: ChatItem, _ maxWidth: CGFloat) -> some View {
|
||||
if case let .groupRcv(member) = ci.chatDir,
|
||||
case let .group(groupInfo) = chat.chatInfo {
|
||||
let prevItem = chatModel.getPrevChatItem(ci)
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
let showMember = prevItem == nil || showMemberImage(member, prevItem)
|
||||
if showMember {
|
||||
ProfileImage(imageStr: member.memberProfile.image)
|
||||
.frame(width: memberImageSize, height: memberImageSize)
|
||||
.onTapGesture { selectedMember = member }
|
||||
.appSheet(item: $selectedMember) { member in
|
||||
GroupMemberInfoView(groupInfo: groupInfo, member: member, navigation: true)
|
||||
}
|
||||
let nextItem = chatModel.getNextChatItem(ci)
|
||||
if ci.isMemberConnected != nil && (nextItem?.isMemberConnected != nil) {
|
||||
EmptyView()
|
||||
} else {
|
||||
let prevItem = chatModel.getPrevChatItem(ci)
|
||||
if ci.isMemberConnected != nil,
|
||||
let prevItem = prevItem,
|
||||
let prevMember = prevItem.isMemberConnected {
|
||||
membersConnectedItem(ci, maxWidth, member, prevMember, prevItem)
|
||||
} else {
|
||||
Rectangle().fill(.clear)
|
||||
.frame(width: memberImageSize, height: memberImageSize)
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
let showMember = prevItem == nil || showMemberImage(member, prevItem)
|
||||
if showMember {
|
||||
ProfileImage(imageStr: member.memberProfile.image)
|
||||
.frame(width: memberImageSize, height: memberImageSize)
|
||||
.onTapGesture { selectedMember = member }
|
||||
.appSheet(item: $selectedMember) { member in
|
||||
GroupMemberInfoView(groupInfo: groupInfo, member: member, navigation: true)
|
||||
}
|
||||
} else {
|
||||
Rectangle().fill(.clear)
|
||||
.frame(width: memberImageSize, height: memberImageSize)
|
||||
}
|
||||
ChatItemWithMenu(
|
||||
ci: ci,
|
||||
showMember: showMember,
|
||||
maxWidth: maxWidth,
|
||||
scrollProxy: scrollProxy,
|
||||
deleteMessage: deleteMessage,
|
||||
deletingItem: $deletingItem,
|
||||
composeState: $composeState,
|
||||
showDeleteMessage: $showDeleteMessage
|
||||
)
|
||||
.padding(.leading, 8)
|
||||
.environmentObject(chat)
|
||||
}
|
||||
.padding(.trailing)
|
||||
.padding(.leading, 12)
|
||||
}
|
||||
ChatItemWithMenu(
|
||||
ci: ci,
|
||||
showMember: showMember,
|
||||
maxWidth: maxWidth,
|
||||
scrollProxy: scrollProxy,
|
||||
deleteMessage: deleteMessage,
|
||||
deletingItem: $deletingItem,
|
||||
composeState: $composeState,
|
||||
showDeleteMessage: $showDeleteMessage
|
||||
)
|
||||
.padding(.leading, 8)
|
||||
.environmentObject(chat)
|
||||
}
|
||||
.padding(.trailing)
|
||||
.padding(.leading, 12)
|
||||
} else {
|
||||
ChatItemWithMenu(
|
||||
ci: ci,
|
||||
@ -474,6 +485,49 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func membersConnectedItem(
|
||||
_ ci: ChatItem,
|
||||
_ maxWidth: CGFloat,
|
||||
_ member: GroupMember,
|
||||
_ prevMember: GroupMember,
|
||||
_ prevItem: ChatItem
|
||||
) -> some View {
|
||||
let membersConnected: [GroupMember] = [member, prevMember] + collectPrevMembersConnected(prevItem)
|
||||
let replacingItem = ChatItem(
|
||||
chatDir: ci.chatDir,
|
||||
meta: ci.meta,
|
||||
content: .membersConnected(members: membersConnected)
|
||||
)
|
||||
let alignment: Alignment = .leading
|
||||
VStack(alignment: alignment.horizontal, spacing: 3) {
|
||||
ChatItemView(chatInfo: chat.chatInfo, chatItem: replacingItem, maxWidth: maxWidth, scrollProxy: scrollProxy, revealed: .constant(false))
|
||||
}
|
||||
.frame(maxWidth: maxWidth, maxHeight: .infinity, alignment: alignment)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: alignment)
|
||||
.padding(.horizontal)
|
||||
.environmentObject(chat)
|
||||
// return ChatItemWithMenu(
|
||||
// ci: replacingItem,
|
||||
// maxWidth: maxWidth,
|
||||
// scrollProxy: scrollProxy,
|
||||
// deleteMessage: deleteMessage,
|
||||
// deletingItem: $deletingItem,
|
||||
// composeState: $composeState,
|
||||
// showDeleteMessage: $showDeleteMessage
|
||||
// )
|
||||
// .padding(.horizontal)
|
||||
// .environmentObject(chat)
|
||||
}
|
||||
|
||||
private func collectPrevMembersConnected(_ ci: ChatItem) -> [GroupMember] {
|
||||
guard let prevItem = chatModel.getPrevChatItem(ci),
|
||||
let memberConnected = prevItem.isMemberConnected else {
|
||||
return []
|
||||
}
|
||||
let prevMembers = collectPrevMembersConnected(prevItem)
|
||||
return [memberConnected] + prevMembers
|
||||
}
|
||||
|
||||
private struct ChatItemWithMenu: View {
|
||||
@EnvironmentObject var chat: Chat
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
@ -180,6 +180,7 @@
|
||||
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
|
||||
64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */; };
|
||||
64E972072881BB22008DBC02 /* CIGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */; };
|
||||
64EC940B2A86805D0025EAA3 /* CIMembersConnectedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EC940A2A86805D0025EAA3 /* CIMembersConnectedView.swift */; };
|
||||
64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */; };
|
||||
D7197A1829AE89660055C05A /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = D7197A1729AE89660055C05A /* WebRTC */; };
|
||||
D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A9087294BD7A70047C86D /* NativeTextEditor.swift */; };
|
||||
@ -459,6 +460,7 @@
|
||||
64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactLearnMore.swift; sourceTree = "<group>"; };
|
||||
64DAE1502809D9F5000DA960 /* FileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = "<group>"; };
|
||||
64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIGroupInvitationView.swift; sourceTree = "<group>"; };
|
||||
64EC940A2A86805D0025EAA3 /* CIMembersConnectedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMembersConnectedView.swift; sourceTree = "<group>"; };
|
||||
64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncognitoHelp.swift; sourceTree = "<group>"; };
|
||||
D72A9087294BD7A70047C86D /* NativeTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeTextEditor.swift; sourceTree = "<group>"; };
|
||||
D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
@ -823,6 +825,7 @@
|
||||
6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */,
|
||||
18415FD2E36F13F596A45BB4 /* CIVideoView.swift */,
|
||||
5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */,
|
||||
64EC940A2A86805D0025EAA3 /* CIMembersConnectedView.swift */,
|
||||
);
|
||||
path = ChatItem;
|
||||
sourceTree = "<group>";
|
||||
@ -1145,6 +1148,7 @@
|
||||
5C7505A527B679EE00BE3227 /* NavLinkPlain.swift in Sources */,
|
||||
5C58BCD6292BEBE600AF9E4F /* CIChatFeatureView.swift in Sources */,
|
||||
5CB346E72868D76D001FD2EF /* NotificationsView.swift in Sources */,
|
||||
64EC940B2A86805D0025EAA3 /* CIMembersConnectedView.swift in Sources */,
|
||||
647F090E288EA27B00644C40 /* GroupMemberInfoView.swift in Sources */,
|
||||
646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */,
|
||||
5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */,
|
||||
|
@ -2009,6 +2009,17 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
}
|
||||
}
|
||||
|
||||
public var isMemberConnected: GroupMember? {
|
||||
switch chatDir {
|
||||
case .groupRcv(let groupMember):
|
||||
switch content {
|
||||
case .rcvGroupEvent(rcvGroupEvent: .memberConnected): return groupMember
|
||||
default: return nil
|
||||
}
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
private var showNtfDir: Bool {
|
||||
return !chatDir.sent
|
||||
}
|
||||
@ -2052,6 +2063,7 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
case .sndModerated: return true
|
||||
case .rcvModerated: return true
|
||||
case .invalidJSON: return false
|
||||
case .membersConnected: return false
|
||||
}
|
||||
}
|
||||
|
||||
@ -2173,6 +2185,16 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
)
|
||||
}
|
||||
|
||||
public static func getMembersConnectedSample () -> ChatItem {
|
||||
ChatItem(
|
||||
chatDir: .groupRcv(groupMember: GroupMember.sampleData),
|
||||
meta: CIMeta.getSample(1, .now, "group event text", .rcvRead),
|
||||
content: .membersConnected(members: [GroupMember.sampleData, GroupMember.sampleData]),
|
||||
quotedItem: nil,
|
||||
file: nil
|
||||
)
|
||||
}
|
||||
|
||||
public static func deletedItemDummy() -> ChatItem {
|
||||
ChatItem(
|
||||
chatDir: CIDirection.directRcv,
|
||||
@ -2451,6 +2473,7 @@ public enum CIContent: Decodable, ItemContent {
|
||||
case sndModerated
|
||||
case rcvModerated
|
||||
case invalidJSON(json: String)
|
||||
case membersConnected(members: [GroupMember])
|
||||
|
||||
public var text: String {
|
||||
get {
|
||||
@ -2480,10 +2503,24 @@ public enum CIContent: Decodable, ItemContent {
|
||||
case .sndModerated: return NSLocalizedString("moderated", comment: "moderated chat item")
|
||||
case .rcvModerated: return NSLocalizedString("moderated", comment: "moderated chat item")
|
||||
case .invalidJSON: return NSLocalizedString("invalid data", comment: "invalid chat item")
|
||||
case let .membersConnected(members):
|
||||
if members.count > 4 {
|
||||
return String.localizedStringWithFormat(
|
||||
"%@ and %d other members connected",
|
||||
CIContent.membersConnectedNames(Array(members.prefix(3))),
|
||||
members.count - 3
|
||||
)
|
||||
} else {
|
||||
return String.localizedStringWithFormat("%@ members connected", CIContent.membersConnectedNames(members))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func membersConnectedNames(_ members: [GroupMember]) -> String {
|
||||
members.map { $0.chatViewName }.joined(separator: ", ")
|
||||
}
|
||||
|
||||
static func featureText(_ feature: Feature, _ enabled: String, _ param: Int?) -> String {
|
||||
feature.hasParam
|
||||
? "\(feature.text): \(timeText(param))"
|
||||
|
Loading…
Reference in New Issue
Block a user