Files
element-x-ios/ElementX/Sources/Other/Extensions/Array.swift
T

82 lines
3.1 KiB
Swift

//
// Copyright 2025 Element Creations Ltd.
// Copyright 2023-2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import Foundation
nonisolated extension Array {
func groupBy(_ isGroupable: (Element) -> Bool) -> [[Element]] {
var newItems = [[Element]]()
// Cache groupable states to avoid recomputing them later
let groupableStateByIndex = map(isGroupable)
var itemsToBeGrouped = [Element]()
for (index, currentItem) in enumerated() {
let previousItem = self[safe: index - 1]
let nextItem = self[safe: index + 1]
if groupableStateByIndex[safe: index] == true {
// At the begining of a groupable slice, very first message
if previousItem == nil, groupableStateByIndex[safe: index + 1] == true {
itemsToBeGrouped.append(currentItem)
}
// Still at the begining of a groupable slice
else if groupableStateByIndex[safe: index - 1] == false, groupableStateByIndex[safe: index + 1] == true {
itemsToBeGrouped.append(currentItem)
}
// In the middle of a groupable slice
else if !itemsToBeGrouped.isEmpty {
itemsToBeGrouped.append(currentItem)
}
// Solitary groupable item
else {
newItems.append([currentItem])
}
// Last item in the list. Try finishing the groupable slice
if nextItem == nil, !itemsToBeGrouped.isEmpty {
newItems.append(itemsToBeGrouped)
itemsToBeGrouped.removeAll()
}
} else {
// Finished a groupable slice
if !itemsToBeGrouped.isEmpty {
newItems.append(itemsToBeGrouped)
itemsToBeGrouped.removeAll()
}
// Append the current element too
newItems.append([currentItem])
}
}
return newItems
}
}
nonisolated extension Array where Element == RoomTimelineItemProtocol {
func firstUsingStableID(_ id: TimelineItemIdentifier) -> Element? {
first { $0.id.uniqueID == id.uniqueID }
}
/// The voice message that directly follows the item with the given ID, if there is one.
///
/// Decorations such as date separators and the read marker are ignored, but any other
/// kind of item in between means that no voice message directly follows.
func voiceMessageDirectlyFollowing(_ id: TimelineItemIdentifier) -> VoiceMessageRoomTimelineItem? {
guard let index = firstIndex(where: { $0.id.uniqueID == id.uniqueID }) else { return nil }
for item in self[index...].dropFirst() where !(item is DecorationTimelineItemProtocol) {
return item as? VoiceMessageRoomTimelineItem
}
return nil
}
}