ios: members connected aggregated item
This commit is contained in:
parent
1bc880877d
commit
08ad0ecf21
@ -490,6 +490,14 @@ final class ChatModel: ObservableObject {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getNextChatItem(_ ci: ChatItem) -> ChatItem? {
|
||||||
|
if let i = getChatItemIndex(ci), i - 1 >= 0 {
|
||||||
|
return reversedChatItems[i - 1]
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func popChat(_ id: String) {
|
func popChat(_ id: String) {
|
||||||
if let i = getChatIndex(id) {
|
if let i = getChatIndex(id) {
|
||||||
|
@ -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 .sndModerated: deletedItemView()
|
||||||
case .rcvModerated: deletedItemView()
|
case .rcvModerated: deletedItemView()
|
||||||
case let .invalidJSON(json): CIInvalidJSONView(json: json)
|
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 SimpleXChat
|
||||||
import SwiftyGif
|
import SwiftyGif
|
||||||
|
|
||||||
private let memberImageSize: CGFloat = 34
|
let memberImageSize: CGFloat = 34
|
||||||
|
|
||||||
struct ChatView: View {
|
struct ChatView: View {
|
||||||
@EnvironmentObject var chatModel: ChatModel
|
@EnvironmentObject var chatModel: ChatModel
|
||||||
@ -430,35 +430,46 @@ struct ChatView: View {
|
|||||||
@ViewBuilder private func chatItemView(_ ci: ChatItem, _ maxWidth: CGFloat) -> some View {
|
@ViewBuilder private func chatItemView(_ ci: ChatItem, _ maxWidth: CGFloat) -> some View {
|
||||||
if case let .groupRcv(member) = ci.chatDir,
|
if case let .groupRcv(member) = ci.chatDir,
|
||||||
case let .group(groupInfo) = chat.chatInfo {
|
case let .group(groupInfo) = chat.chatInfo {
|
||||||
let prevItem = chatModel.getPrevChatItem(ci)
|
let nextItem = chatModel.getNextChatItem(ci)
|
||||||
HStack(alignment: .top, spacing: 0) {
|
if ci.isMemberConnected != nil && (nextItem?.isMemberConnected != nil) {
|
||||||
let showMember = prevItem == nil || showMemberImage(member, prevItem)
|
EmptyView()
|
||||||
if showMember {
|
} else {
|
||||||
ProfileImage(imageStr: member.memberProfile.image)
|
let prevItem = chatModel.getPrevChatItem(ci)
|
||||||
.frame(width: memberImageSize, height: memberImageSize)
|
if ci.isMemberConnected != nil,
|
||||||
.onTapGesture { selectedMember = member }
|
let prevItem = prevItem,
|
||||||
.appSheet(item: $selectedMember) { member in
|
let prevMember = prevItem.isMemberConnected {
|
||||||
GroupMemberInfoView(groupInfo: groupInfo, member: member, navigation: true)
|
membersConnectedItem(ci, maxWidth, member, prevMember, prevItem)
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Rectangle().fill(.clear)
|
HStack(alignment: .top, spacing: 0) {
|
||||||
.frame(width: memberImageSize, height: memberImageSize)
|
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 {
|
} else {
|
||||||
ChatItemWithMenu(
|
ChatItemWithMenu(
|
||||||
ci: ci,
|
ci: ci,
|
||||||
@ -473,6 +484,49 @@ struct ChatView: View {
|
|||||||
.environmentObject(chat)
|
.environmentObject(chat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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 {
|
private struct ChatItemWithMenu: View {
|
||||||
@EnvironmentObject var chat: Chat
|
@EnvironmentObject var chat: Chat
|
||||||
|
@ -180,6 +180,7 @@
|
|||||||
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
|
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
|
||||||
64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */; };
|
64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */; };
|
||||||
64E972072881BB22008DBC02 /* CIGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E972062881BB22008DBC02 /* CIGroupInvitationView.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 */; };
|
64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */; };
|
||||||
D7197A1829AE89660055C05A /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = D7197A1729AE89660055C05A /* WebRTC */; };
|
D7197A1829AE89660055C05A /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = D7197A1729AE89660055C05A /* WebRTC */; };
|
||||||
D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A9087294BD7A70047C86D /* NativeTextEditor.swift */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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 */,
|
6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */,
|
||||||
18415FD2E36F13F596A45BB4 /* CIVideoView.swift */,
|
18415FD2E36F13F596A45BB4 /* CIVideoView.swift */,
|
||||||
5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */,
|
5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */,
|
||||||
|
64EC940A2A86805D0025EAA3 /* CIMembersConnectedView.swift */,
|
||||||
);
|
);
|
||||||
path = ChatItem;
|
path = ChatItem;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1145,6 +1148,7 @@
|
|||||||
5C7505A527B679EE00BE3227 /* NavLinkPlain.swift in Sources */,
|
5C7505A527B679EE00BE3227 /* NavLinkPlain.swift in Sources */,
|
||||||
5C58BCD6292BEBE600AF9E4F /* CIChatFeatureView.swift in Sources */,
|
5C58BCD6292BEBE600AF9E4F /* CIChatFeatureView.swift in Sources */,
|
||||||
5CB346E72868D76D001FD2EF /* NotificationsView.swift in Sources */,
|
5CB346E72868D76D001FD2EF /* NotificationsView.swift in Sources */,
|
||||||
|
64EC940B2A86805D0025EAA3 /* CIMembersConnectedView.swift in Sources */,
|
||||||
647F090E288EA27B00644C40 /* GroupMemberInfoView.swift in Sources */,
|
647F090E288EA27B00644C40 /* GroupMemberInfoView.swift in Sources */,
|
||||||
646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */,
|
646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */,
|
||||||
5C7505A227B65FDB00BE3227 /* CIMetaView.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 {
|
private var showNtfDir: Bool {
|
||||||
return !chatDir.sent
|
return !chatDir.sent
|
||||||
}
|
}
|
||||||
@ -2052,6 +2063,7 @@ public struct ChatItem: Identifiable, Decodable {
|
|||||||
case .sndModerated: return true
|
case .sndModerated: return true
|
||||||
case .rcvModerated: return true
|
case .rcvModerated: return true
|
||||||
case .invalidJSON: return false
|
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 {
|
public static func deletedItemDummy() -> ChatItem {
|
||||||
ChatItem(
|
ChatItem(
|
||||||
chatDir: CIDirection.directRcv,
|
chatDir: CIDirection.directRcv,
|
||||||
@ -2451,6 +2473,7 @@ public enum CIContent: Decodable, ItemContent {
|
|||||||
case sndModerated
|
case sndModerated
|
||||||
case rcvModerated
|
case rcvModerated
|
||||||
case invalidJSON(json: String)
|
case invalidJSON(json: String)
|
||||||
|
case membersConnected(members: [GroupMember])
|
||||||
|
|
||||||
public var text: String {
|
public var text: String {
|
||||||
get {
|
get {
|
||||||
@ -2480,10 +2503,24 @@ public enum CIContent: Decodable, ItemContent {
|
|||||||
case .sndModerated: return NSLocalizedString("moderated", comment: "moderated chat item")
|
case .sndModerated: return NSLocalizedString("moderated", comment: "moderated chat item")
|
||||||
case .rcvModerated: 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 .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 {
|
static func featureText(_ feature: Feature, _ enabled: String, _ param: Int?) -> String {
|
||||||
feature.hasParam
|
feature.hasParam
|
||||||
? "\(feature.text): \(timeText(param))"
|
? "\(feature.text): \(timeText(param))"
|
||||||
|
Loading…
Reference in New Issue
Block a user