Skip to content

Commit

Permalink
Monthly media gallery separators (#3601)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
stefanceriu authored Dec 11, 2024
1 parent ee2da53 commit 0b85964
Show file tree
Hide file tree
Showing 55 changed files with 256 additions and 126 deletions.
21 changes: 21 additions & 0 deletions ElementX/Resources/Localizations/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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.";
Expand Down Expand Up @@ -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";
Expand Down
44 changes: 44 additions & 0 deletions ElementX/Sources/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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?
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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())
}
}
}
Loading

0 comments on commit 0b85964

Please sign in to comment.