mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-01 01:03:01 +00:00
82 lines
3.1 KiB
Swift
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
|
|
}
|
|
}
|