Monthly media gallery separators (#3601)

* Fix the order of the items in the media grid.
* Add media gallery timeline separators.
* Change the `SeparatorRoomTimelineItem` to have it expose a Date timestamp instead of a text string.
This commit is contained in:
Stefan Ceriu
2024-12-11 13:32:29 +02:00
committed by GitHub
parent ee2da536af
commit 0b85964f73
55 changed files with 256 additions and 126 deletions
@@ -138,6 +138,7 @@
"common_creating_room" = "Creating room…";
"common_current_user_left_room" = "Left room";
"common_dark" = "Dark";
"common_date_separator_this_month" = "This month";
"common_decryption_error" = "Decryption error";
"common_developer_options" = "Developer options";
"common_device_id" = "Device ID";
@@ -378,13 +379,17 @@
"screen_knock_requests_list_accept_all_alert_description" = "Are you sure you want to accept all requests to join?";
"screen_knock_requests_list_accept_all_alert_title" = "Accept all requests";
"screen_knock_requests_list_accept_all_button_title" = "Accept all";
"screen_knock_requests_list_accept_all_loading_title" = "Accepting all requests to join";
"screen_knock_requests_list_accept_loading_title" = "Accepting request to join";
"screen_knock_requests_list_ban_alert_confirm_button_title" = "Yes, decline and ban";
"screen_knock_requests_list_ban_alert_description" = "Are you sure you want to decline and ban %1$@? This user won’t be able to request access to join this room again.";
"screen_knock_requests_list_ban_alert_title" = "Decline and ban from accessing";
"screen_knock_requests_list_ban_loading_title" = "Declining and banning access";
"screen_knock_requests_list_decline_alert_confirm_button_title" = "Yes, decline";
"screen_knock_requests_list_decline_alert_description" = "Are you sure you want to decline %1$@ request to join this room?";
"screen_knock_requests_list_decline_alert_title" = "Decline access";
"screen_knock_requests_list_decline_and_ban_action_title" = "Decline and ban";
"screen_knock_requests_list_decline_loading_title" = "Declining request to join";
"screen_knock_requests_list_empty_state_description" = "When somebody will ask to join the room, you’ll be able to see their request here.";
"screen_knock_requests_list_empty_state_title" = "No pending request to join";
"screen_knock_requests_list_title" = "Requests to join";
@@ -419,7 +424,22 @@
"screen_room_single_knock_request_view_button_title" = "View";
"screen_room_details_pinned_events_row_title" = "Pinned messages";
"screen_room_details_requests_to_join_title" = "Requests to join";
"screen_room_details_security_and_privacy_title" = "Security & privacy";
"screen_roomlist_knock_event_sent_description" = "Request to join sent";
"screen_security_and_privacy_ask_to_join_option_description" = "Anyone can ask to join the room but an administrator or moderator will have to accept the request.";
"screen_security_and_privacy_ask_to_join_option_title" = "Ask to join";
"screen_security_and_privacy_enable_encryption_alert_confirm_button_title" = "Yes, enable encryption";
"screen_security_and_privacy_enable_encryption_alert_description" = "Once enabled, encryption for a room cannot be disabled, Message history will only be visible for room members since they were invited or since they joined the room.\nNo one besides the room members will be able to read messages. This may prevent bots and bridges to work correctly.\nWe do not recommend enabling encryption for rooms that anyone can find and join.";
"screen_security_and_privacy_enable_encryption_alert_title" = "Enable encryption?";
"screen_security_and_privacy_encryption_section_footer" = "Once enabled, encryption cannot be disabled.";
"screen_security_and_privacy_encryption_section_title" = "Encryption";
"screen_security_and_privacy_encryption_toggle_title" = "Enable end-to-end encryption";
"screen_security_and_privacy_room_access_anyone_option_description" = "Anyone can find and join";
"screen_security_and_privacy_room_access_anyone_option_title" = "Anyone";
"screen_security_and_privacy_room_access_invite_only_option_description" = "People can only join if they are invited";
"screen_security_and_privacy_room_access_invite_only_option_title" = "Invite only";
"screen_security_and_privacy_room_access_section_title" = "Room access";
"screen_security_and_privacy_title" = "Security & privacy";
"screen_timeline_item_menu_send_failure_changed_identity" = "Message not sent because %1$@’s verified identity has changed.";
"screen_timeline_item_menu_send_failure_unsigned_device" = "Message not sent because %1$@ has not verified all devices.";
"screen_timeline_item_menu_send_failure_you_unsigned_device" = "Message not sent because you have not verified one or more of your devices.";
@@ -1004,6 +1024,7 @@
"test_language_identifier" = "en";
"test_untranslated_default_language_identifier" = "en";
"timeline_decryption_failure_historical_event_no_key_backup" = "Historical messages are not available on this device";
"timeline_decryption_failure_historical_event_user_not_joined" = "You don't have access to this message";
"timeline_decryption_failure_unable_to_decrypt" = "Unable to decrypt message";
"timeline_decryption_failure_withheld_unverified" = "This message was blocked either because you did not verify your device or because the sender needs to verify your identity.";
"troubleshoot_notifications_entry_point_section" = "Troubleshoot";
+44
View File
@@ -314,6 +314,8 @@ internal enum L10n {
internal static var commonCurrentUserLeftRoom: String { return L10n.tr("Localizable", "common_current_user_left_room") }
/// Dark
internal static var commonDark: String { return L10n.tr("Localizable", "common_dark") }
/// This month
internal static var commonDateSeparatorThisMonth: String { return L10n.tr("Localizable", "common_date_separator_this_month") }
/// Decryption error
internal static var commonDecryptionError: String { return L10n.tr("Localizable", "common_decryption_error") }
/// Developer options
@@ -1314,6 +1316,10 @@ internal enum L10n {
internal static var screenKnockRequestsListAcceptAllAlertTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_accept_all_alert_title") }
/// Accept all
internal static var screenKnockRequestsListAcceptAllButtonTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_accept_all_button_title") }
/// Accepting all requests to join
internal static var screenKnockRequestsListAcceptAllLoadingTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_accept_all_loading_title") }
/// Accepting request to join
internal static var screenKnockRequestsListAcceptLoadingTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_accept_loading_title") }
/// Yes, decline and ban
internal static var screenKnockRequestsListBanAlertConfirmButtonTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_ban_alert_confirm_button_title") }
/// Are you sure you want to decline and ban %1$@? This user won’t be able to request access to join this room again.
@@ -1322,6 +1328,8 @@ internal enum L10n {
}
/// Decline and ban from accessing
internal static var screenKnockRequestsListBanAlertTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_ban_alert_title") }
/// Declining and banning access
internal static var screenKnockRequestsListBanLoadingTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_ban_loading_title") }
/// Yes, decline
internal static var screenKnockRequestsListDeclineAlertConfirmButtonTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_decline_alert_confirm_button_title") }
/// Are you sure you want to decline %1$@ request to join this room?
@@ -1332,6 +1340,8 @@ internal enum L10n {
internal static var screenKnockRequestsListDeclineAlertTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_decline_alert_title") }
/// Decline and ban
internal static var screenKnockRequestsListDeclineAndBanActionTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_decline_and_ban_action_title") }
/// Declining request to join
internal static var screenKnockRequestsListDeclineLoadingTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_decline_loading_title") }
/// When somebody will ask to join the room, you’ll be able to see their request here.
internal static var screenKnockRequestsListEmptyStateDescription: String { return L10n.tr("Localizable", "screen_knock_requests_list_empty_state_description") }
/// No pending request to join
@@ -1826,6 +1836,8 @@ internal enum L10n {
internal static var screenRoomDetailsRolesAndPermissions: String { return L10n.tr("Localizable", "screen_room_details_roles_and_permissions") }
/// Room name
internal static var screenRoomDetailsRoomNameLabel: String { return L10n.tr("Localizable", "screen_room_details_room_name_label") }
/// Security & privacy
internal static var screenRoomDetailsSecurityAndPrivacyTitle: String { return L10n.tr("Localizable", "screen_room_details_security_and_privacy_title") }
/// Security
internal static var screenRoomDetailsSecurityTitle: String { return L10n.tr("Localizable", "screen_room_details_security_title") }
/// Share room
@@ -2118,6 +2130,36 @@ internal enum L10n {
internal static var screenRoomlistMarkAsUnread: String { return L10n.tr("Localizable", "screen_roomlist_mark_as_unread") }
/// Browse all rooms
internal static var screenRoomlistRoomDirectoryButtonTitle: String { return L10n.tr("Localizable", "screen_roomlist_room_directory_button_title") }
/// Anyone can ask to join the room but an administrator or moderator will have to accept the request.
internal static var screenSecurityAndPrivacyAskToJoinOptionDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_ask_to_join_option_description") }
/// Ask to join
internal static var screenSecurityAndPrivacyAskToJoinOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_ask_to_join_option_title") }
/// Yes, enable encryption
internal static var screenSecurityAndPrivacyEnableEncryptionAlertConfirmButtonTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_enable_encryption_alert_confirm_button_title") }
/// Once enabled, encryption for a room cannot be disabled, Message history will only be visible for room members since they were invited or since they joined the room.
/// No one besides the room members will be able to read messages. This may prevent bots and bridges to work correctly.
/// We do not recommend enabling encryption for rooms that anyone can find and join.
internal static var screenSecurityAndPrivacyEnableEncryptionAlertDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_enable_encryption_alert_description") }
/// Enable encryption?
internal static var screenSecurityAndPrivacyEnableEncryptionAlertTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_enable_encryption_alert_title") }
/// Once enabled, encryption cannot be disabled.
internal static var screenSecurityAndPrivacyEncryptionSectionFooter: String { return L10n.tr("Localizable", "screen_security_and_privacy_encryption_section_footer") }
/// Encryption
internal static var screenSecurityAndPrivacyEncryptionSectionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_encryption_section_title") }
/// Enable end-to-end encryption
internal static var screenSecurityAndPrivacyEncryptionToggleTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_encryption_toggle_title") }
/// Anyone can find and join
internal static var screenSecurityAndPrivacyRoomAccessAnyoneOptionDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_anyone_option_description") }
/// Anyone
internal static var screenSecurityAndPrivacyRoomAccessAnyoneOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_anyone_option_title") }
/// People can only join if they are invited
internal static var screenSecurityAndPrivacyRoomAccessInviteOnlyOptionDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_invite_only_option_description") }
/// Invite only
internal static var screenSecurityAndPrivacyRoomAccessInviteOnlyOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_invite_only_option_title") }
/// Room access
internal static var screenSecurityAndPrivacyRoomAccessSectionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_section_title") }
/// Security & privacy
internal static var screenSecurityAndPrivacyTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_title") }
/// Change account provider
internal static var screenServerConfirmationChangeServer: String { return L10n.tr("Localizable", "screen_server_confirmation_change_server") }
/// A private server for Element employees.
@@ -2526,6 +2568,8 @@ internal enum L10n {
internal static var testUntranslatedDefaultLanguageIdentifier: String { return L10n.tr("Localizable", "test_untranslated_default_language_identifier") }
/// Historical messages are not available on this device
internal static var timelineDecryptionFailureHistoricalEventNoKeyBackup: String { return L10n.tr("Localizable", "timeline_decryption_failure_historical_event_no_key_backup") }
/// You don't have access to this message
internal static var timelineDecryptionFailureHistoricalEventUserNotJoined: String { return L10n.tr("Localizable", "timeline_decryption_failure_historical_event_user_not_joined") }
/// Unable to decrypt message
internal static var timelineDecryptionFailureUnableToDecrypt: String { return L10n.tr("Localizable", "timeline_decryption_failure_unable_to_decrypt") }
/// This message was blocked either because you did not verify your device or because the sender needs to verify your identity.
@@ -14,9 +14,15 @@ enum MediaEventsTimelineScreenMode {
case files
}
struct MediaEventsTimelineGroup: Identifiable {
var id: String
var title: String
var items: [RoomTimelineItemViewState]
}
struct MediaEventsTimelineScreenViewState: BindableState {
var isBackPaginating = false
var items = [RoomTimelineItemViewState]()
var groups = [MediaEventsTimelineGroup]()
var bindings: MediaEventsTimelineScreenViewStateBindings
}
@@ -84,16 +84,41 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
// MARK: - Private
private func updateWithTimelineViewState(_ timelineViewState: TimelineViewState) {
state.items = timelineViewState.timelineState.itemViewStates.filter { itemViewState in
var newGroups = [MediaEventsTimelineGroup]()
var currentItems = [RoomTimelineItemViewState]()
timelineViewState.timelineState.itemViewStates.filter { itemViewState in
switch itemViewState.type {
case .image, .video:
state.bindings.screenMode == .media
case .audio, .file:
case .audio, .file, .voice:
state.bindings.screenMode == .files
case .separator:
true
default:
false
}
}.reversed()
}.reversed().forEach { item in
if case .separator(let item) = item.type {
let group = MediaEventsTimelineGroup(id: item.id.uniqueID.id,
title: titleForDate(item.timestamp),
items: currentItems)
currentItems = []
newGroups.append(group)
} else {
currentItems.append(item)
}
}
if !currentItems.isEmpty {
MXLog.warning("Found ungrouped timeline items, appending them at end.")
let group = MediaEventsTimelineGroup(id: UUID().uuidString,
title: titleForDate(.now),
items: currentItems)
newGroups.append(group)
}
state.groups = newGroups
state.isBackPaginating = (timelineViewState.timelineState.paginationState.backward == .paginating)
backPaginateIfNecessary(paginationStatus: timelineViewState.timelineState.paginationState.backward)
@@ -124,4 +149,12 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
userIndicatorController: userIndicatorController)
state.bindings.mediaPreviewViewModel = viewModel
}
private func titleForDate(_ date: Date) -> String {
if Calendar.current.isDate(date, equalTo: .now, toGranularity: .month) {
L10n.commonDateSeparatorThisMonth
} else {
date.formatted(.dateTime.month(.wide).year())
}
}
}
@@ -33,44 +33,39 @@ struct MediaEventsTimelineScreen: View {
.timelineMediaQuickLook(viewModel: $context.mediaPreviewViewModel)
}
// The scale effects do the following:
// * flip the scrollView vertically to keep the items
// at the bottom and have pagination working properly
// * flip the grid vertically to counteract the scroll view
// but also horizontally to preserve the corect item order
// * flip the items on both axes have them render correctly
@ViewBuilder
private var content: some View {
ScrollView {
Group {
let columns = [GridItem(.adaptive(minimum: 80, maximum: 150), spacing: 1)]
LazyVGrid(columns: columns, alignment: .center, spacing: 1) {
ForEach(context.viewState.items) { item in
Button {
context.send(viewAction: .tappedItem(item))
} label: {
Color.clear // Let the image aspect fill in place
.aspectRatio(1, contentMode: .fill)
.overlay {
viewForTimelineItem(item)
ForEach(context.viewState.groups) { group in
Section(footer: sectionFooterForGroup(group)) {
ForEach(group.items) { item in
Button {
context.send(viewAction: .tappedItem(item))
} label: {
Color.clear // Let the image aspect fill in place
.aspectRatio(1, contentMode: .fill)
.overlay {
viewForTimelineItem(item)
}
.clipped()
.scaleEffect(.init(width: -1, height: -1))
}
.clipped()
.scaleEffect(.init(width: 1, height: -1))
}
}
}
}
.scaleEffect(.init(width: -1, height: 1))
// Needs to be wrapped in a LazyStack otherwise appearance calls don't trigger
LazyVStack(spacing: 0) {
Rectangle()
.frame(height: 44)
.foregroundStyle(.compound.bgCanvasDefault)
.overlay {
if context.viewState.isBackPaginating {
ProgressView()
}
}
.onAppear {
context.send(viewAction: .oldestItemDidAppear)
}
.onDisappear {
context.send(viewAction: .oldestItemDidDisappear)
}
}
header
}
}
.scaleEffect(.init(width: 1, height: -1))
@@ -79,7 +74,37 @@ struct MediaEventsTimelineScreen: View {
}
}
@ViewBuilder func viewForTimelineItem(_ item: RoomTimelineItemViewState) -> some View {
private var header: some View {
// Needs to be wrapped in a LazyStack otherwise appearance calls don't trigger
LazyVStack(spacing: 0) {
ProgressView()
.padding()
.opacity(context.viewState.isBackPaginating ? 1 : 0)
Rectangle()
.frame(height: 1)
.foregroundStyle(.compound.bgCanvasDefault)
.onAppear {
context.send(viewAction: .oldestItemDidAppear)
}
.onDisappear {
context.send(viewAction: .oldestItemDidDisappear)
}
}
}
@ViewBuilder
func sectionFooterForGroup(_ group: MediaEventsTimelineGroup) -> some View {
Text(group.title)
.font(.compound.bodySMSemibold)
.foregroundColor(.compound.textPrimary)
.frame(alignment: .center)
.scaleEffect(.init(width: -1, height: -1))
.padding(.vertical, 16)
}
@ViewBuilder
func viewForTimelineItem(_ item: RoomTimelineItemViewState) -> some View {
switch item.type {
case .image(let timelineItem):
#warning("Make this work for gifs")
@@ -107,8 +132,6 @@ struct MediaEventsTimelineScreen: View {
} else {
playIcon
}
case .separator(let timelineItem):
Text(timelineItem.text)
default:
EmptyView()
}
@@ -169,12 +192,12 @@ struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
NavigationStack {
MediaEventsTimelineScreen(context: mediaViewModel.context)
.previewDisplayName("Media")
}
.previewDisplayName("Media")
NavigationStack {
MediaEventsTimelineScreen(context: filesViewModel.context)
.previewDisplayName("Files")
}
.previewDisplayName("Files")
}
}
@@ -52,8 +52,8 @@ struct CollapsibleRoomTimelineView: View {
struct CollapsibleRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static let item = CollapsibleTimelineItem(items: [
SeparatorRoomTimelineItem(id: .virtual(uniqueID: .init(id: "First separator")), text: "This is a separator"),
SeparatorRoomTimelineItem(id: .virtual(uniqueID: .init(id: "Second separator")), text: "This is another separator")
SeparatorRoomTimelineItem(id: .virtual(uniqueID: .init(id: "First separator")), timestamp: .mock),
SeparatorRoomTimelineItem(id: .virtual(uniqueID: .init(id: "Second separator")), timestamp: .mock)
])
static var previews: some View {
@@ -33,7 +33,7 @@ struct ReadMarkerRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
VStack(alignment: .leading, spacing: 0) {
RoomTimelineItemView(viewState: .init(type: .separator(.init(id: .virtual(uniqueID: .init(id: "Separator")), text: "Today")), groupStyle: .single))
RoomTimelineItemView(viewState: .init(type: .separator(.init(id: .virtual(uniqueID: .init(id: "Separator")), timestamp: .mock)), groupStyle: .single))
RoomTimelineItemView(viewState: .init(type: .text(.init(id: .randomEvent,
timestamp: .mock,
isOutgoing: true,
@@ -45,7 +45,7 @@ struct ReadMarkerRoomTimelineView_Previews: PreviewProvider, TestablePreview {
ReadMarkerRoomTimelineView(timelineItem: item)
RoomTimelineItemView(viewState: .init(type: .separator(.init(id: .virtual(uniqueID: .init(id: "Separator")), text: "Today")), groupStyle: .single))
RoomTimelineItemView(viewState: .init(type: .separator(.init(id: .virtual(uniqueID: .init(id: "Separator")), timestamp: .mock)), groupStyle: .single))
RoomTimelineItemView(viewState: .init(type: .text(.init(id: .randomEvent,
timestamp: .mock,
isOutgoing: false,
@@ -11,7 +11,7 @@ struct SeparatorRoomTimelineView: View {
let timelineItem: SeparatorRoomTimelineItem
var body: some View {
Text(timelineItem.text)
Text(timelineItem.timestamp.formatted(date: .complete, time: .omitted))
.font(.compound.bodySMSemibold)
.foregroundColor(.compound.textPrimary)
.frame(maxWidth: .infinity)
@@ -24,7 +24,7 @@ struct SeparatorRoomTimelineView: View {
struct SeparatorRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
let item = SeparatorRoomTimelineItem(id: .virtual(uniqueID: .init(id: "Separator")),
text: "This is a separator")
timestamp: .mock)
SeparatorRoomTimelineView(timelineItem: item)
}
}
@@ -10,7 +10,7 @@ import Foundation
enum RoomTimelineItemFixtures {
/// The default timeline items used in Xcode previews etc.
static var `default`: [RoomTimelineItemProtocol] = [
SeparatorRoomTimelineItem(id: .virtual(uniqueID: .init(id: "Yesterday")), text: "Yesterday"),
SeparatorRoomTimelineItem(id: .virtual(uniqueID: .init(id: "Yesterday")), timestamp: .mock),
TextRoomTimelineItem(id: .event(uniqueID: .init(id: ".RoomTimelineItemFixtures.default.0"),
eventOrTransactionID: .eventId(eventId: "RoomTimelineItemFixtures.default.0")),
timestamp: .mock,
@@ -52,7 +52,7 @@ enum RoomTimelineItemFixtures {
ReactionSender(id: "jacob", timestamp: Date())
])
])),
SeparatorRoomTimelineItem(id: .virtual(uniqueID: .init(id: "Today")), text: "Today"),
SeparatorRoomTimelineItem(id: .virtual(uniqueID: .init(id: "Today")), timestamp: .mock),
TextRoomTimelineItem(id: .event(uniqueID: .init(id: "RoomTimelineItemFixtures.default.3"),
eventOrTransactionID: .eventId(eventId: "RoomTimelineItemFixtures.default.3")),
timestamp: .mock,
@@ -262,6 +262,11 @@ enum RoomTimelineItemFixtures {
VoiceMessageRoomTimelineItem(isOutgoing: true)
]
}
static var separator: SeparatorRoomTimelineItem {
SeparatorRoomTimelineItem(id: .virtual(uniqueID: .init(id: UUID().uuidString)),
timestamp: .now)
}
}
private extension TextRoomTimelineItem {
@@ -42,7 +42,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
switch timelineKind {
case .media:
timelineItems = (0..<5).reduce([]) { partialResult, _ in
partialResult + RoomTimelineItemFixtures.mediaChunk
partialResult + [RoomTimelineItemFixtures.separator] + RoomTimelineItemFixtures.mediaChunk
}
default:
break
@@ -472,9 +472,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
switch virtualItem {
case .dateDivider(let timestamp):
let date = Date(timeIntervalSince1970: TimeInterval(timestamp / 1000))
let dateString = date.formatted(date: .complete, time: .omitted)
return SeparatorRoomTimelineItem(id: .virtual(uniqueID: uniqueID), text: dateString)
return SeparatorRoomTimelineItem(id: .virtual(uniqueID: uniqueID), timestamp: date)
case .readMarker:
return ReadMarkerRoomTimelineItem(id: .virtual(uniqueID: uniqueID))
}
@@ -9,5 +9,5 @@ import Foundation
struct SeparatorRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let text: String
let timestamp: Date
}
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1a72db00d98af34232f15be756e5f2358920ea8b2c327949aece97755105779a
size 335736
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:da72b8fd77d8040d6d53cf4f8b02b36b1ebd4954b25c6487f7874df015f743eb
size 72305
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:472cdd02918531822503fbfb067e11ae24b8ad7ee510efa8b23d2dd17be920d1
size 84907
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:99cb5be30a32dd9b4d29be2c7c2788256b9a4afadd73b3a1ee9b1d8bb25a67d9
size 726687
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:de9881a48786a790f314fd25e1e5f76e2d91156c42d5d49337082c8023ac0de5
size 336616
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cbdceca0d1cf597d6c1c31abb0146084fde5d183ab5acf8399badb6c1b6c7caf
size 73391
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5882b637b51ce4e8d59dcec3779b2ebb37ba1f4db9f04d0a1cab50161b2baa71
size 92195
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:135f21259bdf8324ef4455b75f185988e22b2ed25d289aba19ee3f3e83ba7e88
size 733767
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5e8c6353145a110c8ef00b32d6ce6edf76e093cc1aa3fdb1b62eef41af0b4e1d
size 795122
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9e04f78541a56fcc68d4d96c4cb3c031c577fce926953710cedc515b3e0cd219
size 31710
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:71159e727f3bb47533674c0fcd05128e6ff561ba9d54ab30ba14bc40af2875a3
size 38785
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fc96fc9a2ed0d4c6ddce7a4d84243f157f11f70d4d339bbd6fa59de874e26d0a
size 808526
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:696f15ca06f05895a003243b041107724143bbe0815147420d50ac475952c205
size 795870
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9db638196c632227dc675d0d599d13e57d76ecf2cc7b310f94dab046316e7921
size 32636
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:28bb922273d38dea5b8444e16f2df180cdd73550f318112fa83919ef9b5db90c
size 42692
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8581f27e004fae35ea17368cc70aa20b8add4cf2fe222275b5385152f9dd04e2
size 814086
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a8f68e3f488d0c469a5d368a2194eda034d3b8212be3a07049729dfb7eaf1e9e
size 88436
oid sha256:5caa285f573ff5ed5a8c1c7e4df0015421b415fbb985865c0500c71814cce511
size 94735
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a63c1e2da2e645d36385134db96726edfa1ececcbc10dfbcceead1597717a0a2
size 90906
oid sha256:a834120a6cb86890e9a04130f722c45a184b232d8cecc982284b8b216848b228
size 97871
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:832a4a1def3a55e42cbf7852948afe4a075350a9eaa53393634e81f9b9a62964
size 46672
oid sha256:f5a8955e619a6eb923d9504da81039e28a8e4ab14d570b33154b997c9311804e
size 51927
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bd1d50456c64590a25778c41769444d428315d0ae36e0d030222a2701fda37e3
size 49172
oid sha256:d682c9f79ae6ff7271c46f62f3fd453c30e65962efb5aa4bcdacf5566e99278c
size 55130
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f891ab4ce086e3980af0f654017f6cf763a825e43a813ef1c9a443e2b8c58a47
size 293068
oid sha256:d19fdc59c123919099ce464a0bb5478edb8df05e23609cd5dae1b430bcd469d1
size 295900
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f1ae23a50e86f3da569b219703801262759688ebb5d37fe90502622778b9c9f9
size 301242
oid sha256:015366f6fe40da0753b014c0be10d6f46478d36e76fef8ac0d03608c44c8665d
size 304454
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d7a7290cf6e13af92c9b92257cd45148b7c96230ccc12042ed77f465f41902a8
size 179656
oid sha256:e5fe32d96cc563369893c88d566260a465e5ae7f372b10f75f1cd6b9fa93877e
size 181804
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:be0cce1962d880b7b07250a314ccf5e135439a0eb663e70df1d2557816a0477e
size 177493
oid sha256:6332e9688b6bc74fd053be710d9fc160016ff08fe08d755420871e5a79ad6c24
size 180191
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6180f57ce6e161d79a0c52d1ab86518ae2227979fa806011f66c30869fbee81f
size 70278
oid sha256:25fcc57afb1f80279f98ab65aa5e4ed741764e54407034aba329334c65e9878b
size 71653
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6180f57ce6e161d79a0c52d1ab86518ae2227979fa806011f66c30869fbee81f
size 70278
oid sha256:4c244c3d942e34e4a54a07fef6d90f8e6b292ea4f8a79d88c14dd4a38ea0e8dd
size 70977
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b586f05cc0eabe44fbc3a1f9181ebf69b2593d6f9e046c115405d955dee5aff8
size 29598
oid sha256:74d80c2fe62ebd088356b29f3fde14d32f81299a6a1b385ebdff4e115b600395
size 30361
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b586f05cc0eabe44fbc3a1f9181ebf69b2593d6f9e046c115405d955dee5aff8
size 29598
oid sha256:7647c90ac369edd5bbfd9e0cbe5a871791f871d16c8db511f1d4b95a6bbb24a1
size 31058
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c626349c28842ccd498d55f01ac438258e7010503dc3dcfccfae461e47d84dab
size 280376
oid sha256:562da5417d3d83843b934293c59fd15c729c947a11d85ee223f4e2884e575a80
size 285688
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6ebbdefa08186861eeeeed292843731a048a58072d78c1cb2ec83f48e963698d
size 278721
oid sha256:9288fe8187bc7943a7523e0d161cbb74f4cd73a09c06e1e5cf3f19366a3e8084
size 284078
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:de0ee487487365db8f8fe5e408ab77ed750da4124d728eb97ca7614e8792fe25
size 287499
oid sha256:9c84f555adb18536c418b82b978cb7ef9175fbae00f18e40d7200f8d69a20533
size 292926
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e6b2cfaf19b5c650ea26d3d0e337a928d1d08459304936b6f6486b7de8d9d0a4
size 286103
oid sha256:a75402f09fcec7dcb1132c47ca71e0a08e09e9898b2b10389aeed7bae75c4a46
size 291518
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:763f8c4138ed5d0d535341a5b14f6c8fede9324eb088e68ab6874e95762a4d86
size 221889
oid sha256:50f4a76320c1519daa984dd0b19fb965db8199dfd6786a8cf528228799585aeb
size 226204
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:607c63a33ea20f0a24444f7dc808a57eb0a161c496a3187ce4b068534adacfca
size 219309
oid sha256:95f55f16898bbcdfd599633456eb3436e4a79777fb9c6b93064a5b3e0bcd5dd4
size 223638
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ac7908ffe89f4bbaed33ee75c62db9fa515dff746cf5b082a7523d21bfd0806e
size 227388
oid sha256:d2b324da8fb2eb1d0e3a7ed3e9a4c2796622889d964645da75878e7ec2c53ef5
size 232096
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:49fb9ce905498b08e70dd43077aab4913c8fdcdb3aead866f92caececcad3ada
size 224398
oid sha256:a6b94fda33826a8bd56cc220f95496729df82fb17b2c5d34c76f07697010d670
size 229096
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f891ab4ce086e3980af0f654017f6cf763a825e43a813ef1c9a443e2b8c58a47
size 293068
oid sha256:d19fdc59c123919099ce464a0bb5478edb8df05e23609cd5dae1b430bcd469d1
size 295900
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f1ae23a50e86f3da569b219703801262759688ebb5d37fe90502622778b9c9f9
size 301242
oid sha256:015366f6fe40da0753b014c0be10d6f46478d36e76fef8ac0d03608c44c8665d
size 304454
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d7a7290cf6e13af92c9b92257cd45148b7c96230ccc12042ed77f465f41902a8
size 179656
oid sha256:e5fe32d96cc563369893c88d566260a465e5ae7f372b10f75f1cd6b9fa93877e
size 181804
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:be0cce1962d880b7b07250a314ccf5e135439a0eb663e70df1d2557816a0477e
size 177493
oid sha256:6332e9688b6bc74fd053be710d9fc160016ff08fe08d755420871e5a79ad6c24
size 180191
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:daaa6d27b085fffcf1850d7aadcfef328196bb720b6ee1553fe716a03c07ec02
size 292253
oid sha256:479b7eedb90b238436d89ff1c42ad786796692df62c02775c74475742fa3b386
size 295544
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d609af9f7fa3a82716f496731d73e58cf92dd4e673d02c300a21f90d7f64e6f8
size 368793
oid sha256:d02ff8334acfb26b5da2948d4b16a6418bdbd221f25d1366d4ef61038bf66d86
size 372224
@@ -441,7 +441,7 @@ private extension TextRoomTimelineItem {
private extension SeparatorRoomTimelineItem {
init(uniqueID: TimelineUniqueId) {
self.init(id: .virtual(uniqueID: uniqueID), text: "")
self.init(id: .virtual(uniqueID: uniqueID), timestamp: .mock)
}
}