Room screen: timeline message bubbles (#91)

* #34 Create `TimelineItemStylerView`

* #34 Add styler view into different type of cells

* #34 Use placeholder avatar image on room list

* #34 Add `isOutgoing` param to event based timeline item

* #34 Compute `isOutgoing` for timeline items

* #34 Update sender info view in timeline

* #34 Update mock timeline items

* #34 Rename `EventBasedTimelineView` to `EventBasedTimelineSenderView`

* #34 Change padding on timeline cells

* #34 Create `TimelineItemStylerView` to move content into a bubble if needed

* #34 Use styler view in all of the timeline item views

* #34 Make timestamp more readable on images

* #34 Little layout tweaks

* #34 Add changelog

* #34 Fix code smells

* #34 Set text colors on timeline items

* #34 Fix background color of the timeline

* #34 Fix PR remarks

* #34 Set background colors explicitly on remaining screens

* #34 Reduce min bubble width and make it a scaled metric

* #34 Refactor `PlaceholderAvatarImage` to accept a text only

* #34 Fix code smell

* #34 Fix further comments
This commit is contained in:
ismailgulek
2022-06-23 14:54:29 +03:00
committed by GitHub
parent 5df1411a7e
commit b6b8b4be26
28 changed files with 451 additions and 162 deletions
@@ -13,11 +13,31 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
var timelineItems: [RoomTimelineItemProtocol] = [SeparatorRoomTimelineItem(id: UUID().uuidString, text: "Yesterday"),
TextRoomTimelineItem(id: UUID().uuidString, text: "You rock!", timestamp: "10:10 AM", shouldShowSenderDetails: true, senderId: "Alice"),
TextRoomTimelineItem(id: UUID().uuidString, text: "You also rule!", timestamp: "10:11 AM", shouldShowSenderDetails: false, senderId: "Alice"),
SeparatorRoomTimelineItem(id: UUID().uuidString, text: "Today"),
TextRoomTimelineItem(id: UUID().uuidString, text: "You too!", timestamp: "5 PM", shouldShowSenderDetails: true, senderId: "Bob")]
var timelineItems: [RoomTimelineItemProtocol] = [SeparatorRoomTimelineItem(id: UUID().uuidString,
text: "Yesterday"),
TextRoomTimelineItem(id: UUID().uuidString,
text: "You rock!",
timestamp: "10:10 AM",
shouldShowSenderDetails: true,
isOutgoing: false,
senderId: "",
senderDisplayName: "Some user with a really long long long long long display name"),
TextRoomTimelineItem(id: UUID().uuidString,
text: "You also rule!",
timestamp: "10:11 AM",
shouldShowSenderDetails: false,
isOutgoing: false,
senderId: "",
senderDisplayName: "Alice"),
SeparatorRoomTimelineItem(id: UUID().uuidString,
text: "Today"),
TextRoomTimelineItem(id: UUID().uuidString,
text: "You too!",
timestamp: "5 PM",
shouldShowSenderDetails: false,
isOutgoing: true,
senderId: "",
senderDisplayName: "Bob")]
func paginateBackwards(_ count: UInt) async -> Result<Void, RoomTimelineControllerError> {
return .failure(.generic)
@@ -11,6 +11,7 @@ import Combine
import UIKit
class RoomTimelineController: RoomTimelineControllerProtocol {
private let userId: String
private let timelineProvider: RoomTimelineProviderProtocol
private let timelineItemFactory: RoomTimelineItemFactoryProtocol
private let mediaProvider: MediaProviderProtocol
@@ -22,10 +23,12 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
private(set) var timelineItems = [RoomTimelineItemProtocol]()
init(timelineProvider: RoomTimelineProviderProtocol,
init(userId: String,
timelineProvider: RoomTimelineProviderProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol,
memberDetailProvider: MemberDetailProviderProtocol) {
self.userId = userId
self.timelineProvider = timelineProvider
self.timelineItemFactory = timelineItemFactory
self.mediaProvider = mediaProvider
@@ -110,7 +113,9 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
let areMessagesFromTheSameSender = (previousMessage?.sender == message.sender)
let shouldShowSenderDetails = !areMessagesFromTheSameSender || !areMessagesFromTheSameDay
newTimelineItems.append(timelineItemFactory.buildTimelineItemFor(message: message, showSenderDetails: shouldShowSenderDetails))
newTimelineItems.append(timelineItemFactory.buildTimelineItemFor(message: message,
isOutgoing: message.sender == userId,
showSenderDetails: shouldShowSenderDetails))
previousMessage = message
}
@@ -13,6 +13,7 @@ protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol {
var text: String { get }
var timestamp: String { get }
var shouldShowSenderDetails: Bool { get }
var isOutgoing: Bool { get }
var senderId: String { get }
var senderDisplayName: String? { get set }
@@ -15,6 +15,7 @@ struct EmoteRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equa
var attributedComponents: [AttributedStringBuilderComponent]?
let timestamp: String
let shouldShowSenderDetails: Bool
let isOutgoing: Bool
let senderId: String
var senderDisplayName: String?
@@ -14,6 +14,7 @@ struct ImageRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equa
let text: String
let timestamp: String
let shouldShowSenderDetails: Bool
let isOutgoing: Bool
let senderId: String
var senderDisplayName: String?
@@ -15,6 +15,7 @@ struct NoticeRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equ
var attributedComponents: [AttributedStringBuilderComponent]?
let timestamp: String
let shouldShowSenderDetails: Bool
let isOutgoing: Bool
let senderId: String
var senderDisplayName: String?
@@ -15,6 +15,7 @@ struct TextRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equat
var attributedComponents: [AttributedStringBuilderComponent]?
let timestamp: String
let shouldShowSenderDetails: Bool
let isOutgoing: Bool
let senderId: String
var senderDisplayName: String?
@@ -22,20 +22,20 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
self.attributedStringBuilder = attributedStringBuilder
}
func buildTimelineItemFor(message: RoomMessageProtocol, showSenderDetails: Bool) -> RoomTimelineItemProtocol {
func buildTimelineItemFor(message: RoomMessageProtocol, isOutgoing: Bool, showSenderDetails: Bool) -> RoomTimelineItemProtocol {
let displayName = memberDetailProvider.displayNameForUserId(message.sender)
let avatarURL = memberDetailProvider.avatarURLStringForUserId(message.sender)
let avatarImage = mediaProvider.imageFromURLString(avatarURL)
switch message {
case let message as TextRoomMessage:
return buildTextTimelineItemFromMessage(message, showSenderDetails, displayName, avatarImage)
return buildTextTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage)
case let message as ImageRoomMessage:
return buildImageTimelineItemFromMessage(message, showSenderDetails, displayName, avatarImage)
return buildImageTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage)
case let message as NoticeRoomMessage:
return buildNoticeTimelineItemFromMessage(message, showSenderDetails, displayName, avatarImage)
return buildNoticeTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage)
case let message as EmoteRoomMessage:
return buildEmoteTimelineItemFromMessage(message, showSenderDetails, displayName, avatarImage)
return buildEmoteTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage)
default:
fatalError("Unknown room message.")
}
@@ -43,6 +43,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
// MARK: - Private
private func buildTextTimelineItemFromMessage(_ message: TextRoomMessage,
_ isOutgoing: Bool,
_ showSenderDetails: Bool,
_ displayName: String?,
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
@@ -54,12 +55,14 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
attributedComponents: attributedComponents,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
isOutgoing: isOutgoing,
senderId: message.sender,
senderDisplayName: displayName,
senderAvatar: avatarImage)
}
private func buildImageTimelineItemFromMessage(_ message: ImageRoomMessage,
_ isOutgoing: Bool,
_ showSenderDetails: Bool,
_ displayName: String?,
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
@@ -74,6 +77,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
text: message.body,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
isOutgoing: isOutgoing,
senderId: message.sender,
senderDisplayName: displayName,
senderAvatar: avatarImage,
@@ -86,6 +90,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
}
private func buildNoticeTimelineItemFromMessage(_ message: NoticeRoomMessage,
_ isOutgoing: Bool,
_ showSenderDetails: Bool,
_ displayName: String?,
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
@@ -97,12 +102,14 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
attributedComponents: attributedComponents,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
isOutgoing: isOutgoing,
senderId: message.sender,
senderDisplayName: displayName,
senderAvatar: avatarImage)
}
private func buildEmoteTimelineItemFromMessage(_ message: EmoteRoomMessage,
_ isOutgoing: Bool,
_ showSenderDetails: Bool,
_ displayName: String?,
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
@@ -114,6 +121,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
attributedComponents: attributedComponents,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
isOutgoing: isOutgoing,
senderId: message.sender,
senderDisplayName: displayName,
senderAvatar: avatarImage)
@@ -10,5 +10,5 @@ import Foundation
@MainActor
protocol RoomTimelineItemFactoryProtocol {
func buildTimelineItemFor(message: RoomMessageProtocol, showSenderDetails: Bool) -> RoomTimelineItemProtocol
func buildTimelineItemFor(message: RoomMessageProtocol, isOutgoing: Bool, showSenderDetails: Bool) -> RoomTimelineItemProtocol
}