ios: members connected aggregated item (#2900)
* ios: members connected aggregated item
* wrapping hstack wip
* Revert "wrapping hstack wip"
This reverts commit 75af7473fc
.
* redesign
* fix scroll
* revert
* comment
* remove padding
* brackets
* texts, icon
* optimize - collect only member names
* refactor
* check different index
* refactor 2
---------
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
parent
e326227d06
commit
8dcb70c019
@ -484,13 +484,21 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
|
||||
func getPrevChatItem(_ ci: ChatItem) -> ChatItem? {
|
||||
if let i = getChatItemIndex(ci), i < reversedChatItems.count - 1 {
|
||||
if let i = getChatItemIndex(ci), i + 1 < reversedChatItems.count {
|
||||
return reversedChatItems[i + 1]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getNextChatItem(_ ci: ChatItem) -> ChatItem? {
|
||||
if let i = getChatItemIndex(ci), i > 0 {
|
||||
return reversedChatItems[i - 1]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func popChat(_ id: String) {
|
||||
if let i = getChatIndex(id) {
|
||||
popChat_(i)
|
||||
|
@ -10,20 +10,11 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct CIEventView: View {
|
||||
var chatItem: ChatItem
|
||||
var eventText: Text
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .bottom, spacing: 0) {
|
||||
if let member = chatItem.memberDisplayName {
|
||||
Text(member)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.fontWeight(.light)
|
||||
+ Text(" ")
|
||||
+ chatEventText(chatItem)
|
||||
} else {
|
||||
chatEventText(chatItem)
|
||||
}
|
||||
eventText
|
||||
}
|
||||
.padding(.leading, 6)
|
||||
.padding(.bottom, 6)
|
||||
@ -31,20 +22,8 @@ struct CIEventView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func chatEventText(_ ci: ChatItem) -> Text {
|
||||
Text(ci.content.text)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.fontWeight(.light)
|
||||
+ Text(" ")
|
||||
+ ci.timestampText
|
||||
.font(.caption)
|
||||
.foregroundColor(Color.secondary)
|
||||
.fontWeight(.light)
|
||||
}
|
||||
|
||||
struct CIEventView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CIEventView(chatItem: ChatItem.getGroupEventSample())
|
||||
CIEventView(eventText: Text("event happened"))
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ struct ChatItemView: View {
|
||||
}
|
||||
|
||||
struct ChatItemContentView<Content: View>: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
var chatInfo: ChatInfo
|
||||
var chatItem: ChatItem
|
||||
var showMember: Bool
|
||||
@ -75,6 +76,7 @@ struct ChatItemContentView<Content: View>: View {
|
||||
case let .rcvDecryptionError(msgDecryptError, msgCount): CIRcvDecryptionError(msgDecryptError: msgDecryptError, msgCount: msgCount, chatItem: chatItem, showMember: showMember)
|
||||
case let .rcvGroupInvitation(groupInvitation, memberRole): groupInvitationItemView(groupInvitation, memberRole)
|
||||
case let .sndGroupInvitation(groupInvitation, memberRole): groupInvitationItemView(groupInvitation, memberRole)
|
||||
case .rcvGroupEvent(.memberConnected): CIEventView(eventText: membersConnectedItemText())
|
||||
case .rcvGroupEvent: eventItemView()
|
||||
case .sndGroupEvent: eventItemView()
|
||||
case .rcvConnEvent: eventItemView()
|
||||
@ -108,12 +110,84 @@ struct ChatItemContentView<Content: View>: View {
|
||||
}
|
||||
|
||||
private func eventItemView() -> some View {
|
||||
CIEventView(chatItem: chatItem)
|
||||
return CIEventView(eventText: eventItemViewText())
|
||||
}
|
||||
|
||||
private func eventItemViewText() -> Text {
|
||||
if let member = chatItem.memberDisplayName {
|
||||
return Text(member)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.fontWeight(.light)
|
||||
+ Text(" ")
|
||||
+ chatEventText(chatItem)
|
||||
} else {
|
||||
return chatEventText(chatItem)
|
||||
}
|
||||
}
|
||||
|
||||
private func chatFeatureView(_ feature: Feature, _ iconColor: Color) -> some View {
|
||||
CIChatFeatureView(chatItem: chatItem, feature: feature, iconColor: iconColor)
|
||||
}
|
||||
|
||||
private func membersConnectedItemText() -> Text {
|
||||
if case let .groupRcv(member) = chatItem.chatDir,
|
||||
let prevItem = chatModel.getPrevChatItem(chatItem),
|
||||
let prevMember = prevItem.memberConnected,
|
||||
let membersConnectedText = membersConnectedText(connectedMemberNames(member, prevMember, prevItem)) {
|
||||
return chatEventText(membersConnectedText, chatItem.timestampText)
|
||||
} else {
|
||||
return eventItemViewText()
|
||||
}
|
||||
|
||||
func connectedMemberNames(_ member: GroupMember, _ prevMember: GroupMember, _ prevItem: ChatItem) -> [String] {
|
||||
[member.chatViewName, prevMember.chatViewName] + collectPrevConnectedMemberNames(prevItem)
|
||||
}
|
||||
}
|
||||
|
||||
private func collectPrevConnectedMemberNames(_ ci: ChatItem) -> [String] {
|
||||
guard let prevItem = chatModel.getPrevChatItem(ci),
|
||||
let memberConnected = prevItem.memberConnected else {
|
||||
return []
|
||||
}
|
||||
let prevMemberNames = collectPrevConnectedMemberNames(prevItem)
|
||||
return [memberConnected.chatViewName] + prevMemberNames
|
||||
}
|
||||
|
||||
private func membersConnectedText(_ memberNames: [String]) -> String? {
|
||||
if memberNames.count > 3 {
|
||||
return String.localizedStringWithFormat(
|
||||
NSLocalizedString("%@ and %d other members connected", comment: "<member_names> and <n >= 2> other members connected (plural)"),
|
||||
Array(memberNames.prefix(2)).joined(separator: ", "),
|
||||
memberNames.count - 2
|
||||
)
|
||||
} else if memberNames.count >= 2,
|
||||
let lastMemberName = memberNames.last {
|
||||
return String.localizedStringWithFormat(
|
||||
NSLocalizedString("%@ and %@ connected", comment: "<member_name(s)> and <member_name> connected (plural)"),
|
||||
memberNames.dropLast().joined(separator: ", "),
|
||||
lastMemberName
|
||||
)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func chatEventText(_ eventText: String, _ ts: Text) -> Text {
|
||||
Text(eventText)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.fontWeight(.light)
|
||||
+ Text(" ")
|
||||
+ ts
|
||||
.font(.caption)
|
||||
.foregroundColor(Color.secondary)
|
||||
.fontWeight(.light)
|
||||
}
|
||||
|
||||
func chatEventText(_ ci: ChatItem) -> Text {
|
||||
chatEventText(ci.content.text, ci.timestampText)
|
||||
}
|
||||
|
||||
struct ChatItemView_Previews: PreviewProvider {
|
||||
|
@ -261,7 +261,7 @@ struct ChatView: View {
|
||||
return GeometryReader { g in
|
||||
ScrollViewReader { proxy in
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 5) {
|
||||
LazyVStack(spacing: 0) {
|
||||
ForEach(chatModel.reversedChatItems, id: \.viewId) { ci in
|
||||
let voiceNoFrame = voiceWithoutFrame(ci)
|
||||
let maxWidth = cInfo.chatType == .group
|
||||
@ -430,35 +430,42 @@ 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)
|
||||
}
|
||||
} else {
|
||||
Rectangle().fill(.clear)
|
||||
.frame(width: memberImageSize, height: memberImageSize)
|
||||
let nextItem = chatModel.getNextChatItem(ci)
|
||||
if ci.memberConnected != nil && nextItem?.memberConnected != nil {
|
||||
// memberConnected events are aggregated at the last chat item in a row of such events, see ChatItemView
|
||||
ZStack {} // scroll doesn't work if it's EmptyView()
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
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)
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
.padding(.trailing)
|
||||
.padding(.leading, 12)
|
||||
} else {
|
||||
ChatItemWithMenu(
|
||||
ci: ci,
|
||||
@ -470,6 +477,7 @@ struct ChatView: View {
|
||||
showDeleteMessage: $showDeleteMessage
|
||||
)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 5)
|
||||
.environmentObject(chat)
|
||||
}
|
||||
}
|
||||
|
@ -2021,6 +2021,17 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
}
|
||||
}
|
||||
|
||||
public var memberConnected: 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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user