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:
spaced4ndy 2023-08-14 17:34:22 +04:00 committed by GitHub
parent e326227d06
commit 8dcb70c019
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 138 additions and 58 deletions

View File

@ -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)

View File

@ -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"))
}
}

View File

@ -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 {

View File

@ -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,6 +430,11 @@ 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 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)
@ -459,6 +464,8 @@ struct ChatView: View {
}
.padding(.trailing)
.padding(.leading, 12)
.padding(.bottom, 5)
}
} else {
ChatItemWithMenu(
ci: ci,
@ -470,6 +477,7 @@ struct ChatView: View {
showDeleteMessage: $showDeleteMessage
)
.padding(.horizontal)
.padding(.bottom, 5)
.environmentObject(chat)
}
}

View File

@ -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
}