Some Post-demo Cleanups (#200)

* Remove redundant string

* Use placeholder avatar on home screen

* Add initial home screen ui test

* Fix settings screen PR remarks

* Remove UIKit alert from home screen sign out

* Remove UIKit alert from soft logout clear all data

* Add reference screenshots for home screen UI tests

* Formatting fixes

* Add clearing room method to client proxy

* Clear room proxies on screen dismiss

* Fix retain cycle in room view model

* Do not go into authentication state immediately

* Define sizes for user and room avatars on different screens

* Use defined avatar sizes everywhere

* Disable image disk caching

* Rename rounded corner shape

* Fix text color of placeholder avatars

* Fix PR reviews on formatted body text

* Fix merge conflict

* Remove shouldShowSenderDetails everywhere and just use it from inGroupState

* Remove redundant linter disablings

* Fix PR remarks

* Rename media provider size parameter
This commit is contained in:
ismailgulek
2022-09-23 12:21:41 +03:00
committed by GitHub
parent b9f8fb0b6f
commit bdc83dac27
54 changed files with 307 additions and 204 deletions
@@ -62,8 +62,6 @@ class ClientProxy: ClientProxyProtocol {
private var slidingSyncObserverToken: StoppableSpawn?
private let slidingSync: SlidingSync
private var roomProxies = [String: RoomProxyProtocol]()
let roomSummaryProvider: RoomSummaryProviderProtocol
deinit {
@@ -158,10 +156,6 @@ class ClientProxy: ClientProxyProtocol {
}
func roomForIdentifier(_ identifier: String) -> RoomProxyProtocol? {
if let roomProxy = roomProxies[identifier] {
return roomProxy
}
do {
guard let slidingSyncRoom = try slidingSync.getRoom(roomId: identifier),
let room = slidingSyncRoom.fullRoom() else {
@@ -172,7 +166,6 @@ class ClientProxy: ClientProxyProtocol {
let roomProxy = RoomProxy(slidingSyncRoom: slidingSyncRoom,
room: room,
backgroundTaskService: backgroundTaskService)
roomProxies[identifier] = roomProxy
return roomProxy
} catch {
@@ -180,7 +173,7 @@ class ClientProxy: ClientProxyProtocol {
return nil
}
}
func loadUserDisplayName() async -> Result<String, ClientProxyError> {
await Task.dispatch(on: .global()) {
do {
@@ -30,28 +30,28 @@ struct MediaProvider: MediaProviderProtocol {
self.backgroundTaskService = backgroundTaskService
}
func imageFromSource(_ source: MediaSource?, size: CGSize?) -> UIImage? {
func imageFromSource(_ source: MediaSource?, avatarSize: AvatarSize?) -> UIImage? {
guard let source = source else {
return nil
}
let cacheKey = cacheKeyForURLString(source.underlyingSource.url(), size: size)
let cacheKey = cacheKeyForURLString(source.underlyingSource.url(), avatarSize: avatarSize)
return imageCache.retrieveImageInMemoryCache(forKey: cacheKey, options: nil)
}
func imageFromURLString(_ urlString: String?, size: CGSize?) -> UIImage? {
func imageFromURLString(_ urlString: String?, avatarSize: AvatarSize?) -> UIImage? {
guard let urlString = urlString else {
return nil
}
return imageFromSource(MediaSource(source: clientProxy.mediaSourceForURLString(urlString)), size: size)
return imageFromSource(MediaSource(source: clientProxy.mediaSourceForURLString(urlString)), avatarSize: avatarSize)
}
func loadImageFromURLString(_ urlString: String, size: CGSize?) async -> Result<UIImage, MediaProviderError> {
await loadImageFromSource(MediaSource(source: clientProxy.mediaSourceForURLString(urlString)), size: size)
func loadImageFromURLString(_ urlString: String, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError> {
await loadImageFromSource(MediaSource(source: clientProxy.mediaSourceForURLString(urlString)), avatarSize: avatarSize)
}
func loadImageFromSource(_ source: MediaSource, size: CGSize?) async -> Result<UIImage, MediaProviderError> {
if let image = imageFromSource(source, size: size) {
func loadImageFromSource(_ source: MediaSource, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError> {
if let image = imageFromSource(source, avatarSize: avatarSize) {
return .success(image)
}
@@ -60,7 +60,7 @@ struct MediaProvider: MediaProviderProtocol {
loadImageBgTask?.stop()
}
let cacheKey = cacheKeyForURLString(source.underlyingSource.url(), size: size)
let cacheKey = cacheKeyForURLString(source.underlyingSource.url(), avatarSize: avatarSize)
return await Task.detached { () -> Result<UIImage, MediaProviderError> in
if case let .success(cacheResult) = await imageCache.retrieveImage(forKey: cacheKey),
@@ -70,8 +70,8 @@ struct MediaProvider: MediaProviderProtocol {
do {
let imageData = try await Task.detached { () -> Data in
if let size = size {
return try await clientProxy.loadMediaThumbnailForSource(source.underlyingSource, width: UInt(size.width), height: UInt(size.height))
if let avatarSize = avatarSize {
return try await clientProxy.loadMediaThumbnailForSource(source.underlyingSource, width: UInt(avatarSize.scaledValue), height: UInt(avatarSize.scaledValue))
} else {
return try await clientProxy.loadMediaContentForSource(source.underlyingSource)
}
@@ -96,9 +96,9 @@ struct MediaProvider: MediaProviderProtocol {
// MARK: - Private
private func cacheKeyForURLString(_ urlString: String, size: CGSize?) -> String {
if let size = size {
return "\(urlString){\(size.width),\(size.height)}"
private func cacheKeyForURLString(_ urlString: String, avatarSize: AvatarSize?) -> String {
if let avatarSize = avatarSize {
return "\(urlString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}"
} else {
return urlString
}
@@ -22,33 +22,31 @@ enum MediaProviderError: Error {
case invalidImageData
}
let MediaProviderDefaultAvatarSize = CGSize(width: 44.0, height: 44.0)
@MainActor
protocol MediaProviderProtocol {
func imageFromSource(_ source: MediaSource?, size: CGSize?) -> UIImage?
func imageFromSource(_ source: MediaSource?, avatarSize: AvatarSize?) -> UIImage?
@discardableResult func loadImageFromSource(_ source: MediaSource, size: CGSize?) async -> Result<UIImage, MediaProviderError>
@discardableResult func loadImageFromSource(_ source: MediaSource, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError>
func imageFromURLString(_ urlString: String?, size: CGSize?) -> UIImage?
func imageFromURLString(_ urlString: String?, avatarSize: AvatarSize?) -> UIImage?
@discardableResult func loadImageFromURLString(_ urlString: String, size: CGSize?) async -> Result<UIImage, MediaProviderError>
@discardableResult func loadImageFromURLString(_ urlString: String, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError>
}
extension MediaProviderProtocol {
func imageFromSource(_ source: MediaSource?) -> UIImage? {
imageFromSource(source, size: nil)
imageFromSource(source, avatarSize: nil)
}
@discardableResult func loadImageFromSource(_ source: MediaSource) async -> Result<UIImage, MediaProviderError> {
await loadImageFromSource(source, size: nil)
await loadImageFromSource(source, avatarSize: nil)
}
func imageFromURLString(_ urlString: String?) -> UIImage? {
imageFromURLString(urlString, size: nil)
imageFromURLString(urlString, avatarSize: nil)
}
@discardableResult func loadImageFromURLString(_ urlString: String) async -> Result<UIImage, MediaProviderError> {
await loadImageFromURLString(urlString, size: nil)
await loadImageFromURLString(urlString, avatarSize: nil)
}
}
@@ -18,15 +18,15 @@ import Foundation
import UIKit
struct MockMediaProvider: MediaProviderProtocol {
func imageFromSource(_ source: MediaSource?, size: CGSize?) -> UIImage? {
func imageFromSource(_ source: MediaSource?, avatarSize: AvatarSize?) -> UIImage? {
nil
}
func loadImageFromSource(_ source: MediaSource, size: CGSize?) async -> Result<UIImage, MediaProviderError> {
func loadImageFromSource(_ source: MediaSource, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError> {
.failure(.failedRetrievingImage)
}
func imageFromURLString(_ urlString: String?, size: CGSize?) -> UIImage? {
func imageFromURLString(_ urlString: String?, avatarSize: AvatarSize?) -> UIImage? {
if urlString != nil {
return UIImage(systemName: "photo")
}
@@ -34,7 +34,7 @@ struct MockMediaProvider: MediaProviderProtocol {
return nil
}
func loadImageFromURLString(_ urlString: String, size: CGSize?) async -> Result<UIImage, MediaProviderError> {
func loadImageFromURLString(_ urlString: String, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError> {
.failure(.failedRetrievingImage)
}
}
@@ -42,7 +42,7 @@ class RoomProxy: RoomProxyProtocol {
}()
deinit {
#warning("Should any timeline listeners be removed??")
room.removeTimeline()
}
init(slidingSyncRoom: SlidingSyncRoomProtocol,
@@ -32,7 +32,7 @@ class MockRoomSummaryProvider: RoomSummaryProviderProtocol {
RoomSummary(id: "2",
name: "Second room",
isDirect: true,
avatarURLString: "mockImageURLString",
avatarURLString: nil,
lastMessage: nil,
lastMessageTimestamp: nil,
unreadNotificationCount: 1),
@@ -28,7 +28,6 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
TextRoomTimelineItem(id: UUID().uuidString,
text: "That looks so good!",
timestamp: "10:10 AM",
shouldShowSenderDetails: true,
inGroupState: .single,
isOutgoing: false,
senderId: "",
@@ -37,7 +36,6 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
TextRoomTimelineItem(id: UUID().uuidString,
text: "Let’s get lunch soon! New salad place opened up 🥗. When are y’all free? 🤗",
timestamp: "10:11 AM",
shouldShowSenderDetails: true,
inGroupState: .beginning,
isOutgoing: false,
senderId: "",
@@ -48,7 +46,6 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
TextRoomTimelineItem(id: UUID().uuidString,
text: "I can be around on Wednesday. How about some 🌮 instead? Like https://www.tortilla.co.uk/",
timestamp: "10:11 AM",
shouldShowSenderDetails: false,
inGroupState: .middle,
isOutgoing: false,
senderId: "",
@@ -62,7 +59,6 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
TextRoomTimelineItem(id: UUID().uuidString,
text: "Wow, cool. Ok, lets go the usual place tomorrow?! Is that too soon? Here’s the menu, let me know what you want it’s on me!",
timestamp: "5 PM",
shouldShowSenderDetails: false,
inGroupState: .end,
isOutgoing: false,
senderId: "",
@@ -71,7 +67,6 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
TextRoomTimelineItem(id: UUID().uuidString,
text: "And John's speech was amazing!",
timestamp: "5 PM",
shouldShowSenderDetails: false,
inGroupState: .single,
isOutgoing: true,
senderId: "",
@@ -80,7 +75,6 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
TextRoomTimelineItem(id: UUID().uuidString,
text: "New home office set up!",
timestamp: "5 PM",
shouldShowSenderDetails: false,
inGroupState: .single,
isOutgoing: true,
senderId: "",
@@ -149,7 +149,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
guard eventItem.isMessage || eventItem.isRedacted else { break } // To be handled in the future
newTimelineItems.append(await timelineItemFactory.buildTimelineItemFor(eventItemProxy: eventItem,
showSenderDetails: inGroupState.shouldShowSenderDetails,
inGroupState: inGroupState))
case .virtual:
// case .virtual(let virtualItem):
@@ -252,7 +251,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
return
}
switch await mediaProvider.loadImageFromURLString(avatarURLString, size: MediaProviderDefaultAvatarSize) {
switch await mediaProvider.loadImageFromURLString(avatarURLString, avatarSize: .user(on: .timeline)) {
case .success(let avatar):
guard let index = timelineItems.firstIndex(where: { $0.id == timelineItem.id }),
var item = timelineItems[index] as? EventBasedTimelineItemProtocol else {
@@ -59,3 +59,9 @@ protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol {
var properties: RoomTimelineItemProperties { get }
}
extension EventBasedTimelineItemProtocol {
var shouldShowSenderDetails: Bool {
inGroupState.shouldShowSenderDetails
}
}
@@ -22,7 +22,6 @@ struct EmoteRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equa
let text: String
var attributedComponents: [AttributedStringBuilderComponent]?
let timestamp: String
let shouldShowSenderDetails: Bool
let inGroupState: TimelineItemInGroupState
let isOutgoing: Bool
@@ -21,7 +21,6 @@ struct ImageRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equa
let id: String
let text: String
let timestamp: String
let shouldShowSenderDetails: Bool
let inGroupState: TimelineItemInGroupState
let isOutgoing: Bool
@@ -22,7 +22,6 @@ struct NoticeRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equ
let text: String
var attributedComponents: [AttributedStringBuilderComponent]?
let timestamp: String
let shouldShowSenderDetails: Bool
let inGroupState: TimelineItemInGroupState
let isOutgoing: Bool
@@ -21,7 +21,6 @@ struct RedactedRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, E
let id: String
let text: String
let timestamp: String
let shouldShowSenderDetails: Bool
let inGroupState: TimelineItemInGroupState
let isOutgoing: Bool
@@ -22,7 +22,6 @@ struct TextRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equat
let text: String
var attributedComponents: [AttributedStringBuilderComponent]?
let timestamp: String
let shouldShowSenderDetails: Bool
let inGroupState: TimelineItemInGroupState
let isOutgoing: Bool
@@ -36,15 +36,14 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
}
func buildTimelineItemFor(eventItemProxy: EventTimelineItemProxy,
showSenderDetails: Bool,
inGroupState: TimelineItemInGroupState) async -> RoomTimelineItemProtocol {
let displayName = roomProxy.displayNameForUserId(eventItemProxy.sender)
let avatarURL = roomProxy.avatarURLStringForUserId(eventItemProxy.sender)
let avatarImage = mediaProvider.imageFromURLString(avatarURL, size: MediaProviderDefaultAvatarSize)
let avatarImage = mediaProvider.imageFromURLString(avatarURL, avatarSize: .user(on: .timeline))
let isOutgoing = eventItemProxy.isOwn
if eventItemProxy.isRedacted {
return buildRedactedTimelineItem(eventItemProxy, isOutgoing, showSenderDetails, inGroupState, displayName, avatarImage)
return buildRedactedTimelineItem(eventItemProxy, isOutgoing, inGroupState, displayName, avatarImage)
}
guard let messageContent = eventItemProxy.content.asMessage() else { fatalError("Must be a message for now.") }
@@ -52,35 +51,31 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
switch messageContent.msgtype() {
case .text(content: let content):
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
return await buildTextTimelineItemFromMessage(message, isOutgoing, showSenderDetails, inGroupState, displayName, avatarImage)
return await buildTextTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage)
case .image(content: let content):
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
return await buildImageTimelineItemFromMessage(message, isOutgoing, showSenderDetails, inGroupState, displayName, avatarImage)
return await buildImageTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage)
case .notice(content: let content):
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
return await buildNoticeTimelineItemFromMessage(message, isOutgoing, showSenderDetails, inGroupState, displayName, avatarImage)
return await buildNoticeTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage)
case .emote(content: let content):
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
return await buildEmoteTimelineItemFromMessage(message, isOutgoing, showSenderDetails, inGroupState, displayName, avatarImage)
return await buildEmoteTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage)
case .none:
return await buildFallbackTimelineItem(eventItemProxy, isOutgoing, showSenderDetails, inGroupState, displayName, avatarImage)
return await buildFallbackTimelineItem(eventItemProxy, isOutgoing, inGroupState, displayName, avatarImage)
}
}
// MARK: - Private
// swiftformat:disable function_parameter_count
// swiftlint:disable function_parameter_count
private func buildRedactedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
_ isOutgoing: Bool,
_ showSenderDetails: Bool,
_ inGroupState: TimelineItemInGroupState,
_ displayName: String?,
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
RedactedRoomTimelineItem(id: eventItemProxy.id,
text: ElementL10n.eventRedacted,
timestamp: eventItemProxy.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
inGroupState: inGroupState,
isOutgoing: isOutgoing,
senderId: eventItemProxy.sender,
@@ -91,7 +86,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
private func buildFallbackTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
_ isOutgoing: Bool,
_ showSenderDetails: Bool,
_ inGroupState: TimelineItemInGroupState,
_ displayName: String?,
_ avatarImage: UIImage?) async -> RoomTimelineItemProtocol {
@@ -102,7 +96,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
text: eventItemProxy.body ?? "",
attributedComponents: attributedComponents,
timestamp: eventItemProxy.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
inGroupState: inGroupState,
isOutgoing: isOutgoing,
senderId: eventItemProxy.sender,
@@ -114,7 +107,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
private func buildTextTimelineItemFromMessage(_ message: MessageTimelineItem<TextMessageContent>,
_ isOutgoing: Bool,
_ showSenderDetails: Bool,
_ inGroupState: TimelineItemInGroupState,
_ displayName: String?,
_ avatarImage: UIImage?) async -> RoomTimelineItemProtocol {
@@ -125,7 +117,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
text: message.body,
attributedComponents: attributedComponents,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
inGroupState: inGroupState,
isOutgoing: isOutgoing,
senderId: message.sender,
@@ -137,7 +128,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
private func buildImageTimelineItemFromMessage(_ message: MessageTimelineItem<ImageMessageContent>,
_ isOutgoing: Bool,
_ showSenderDetails: Bool,
_ inGroupState: TimelineItemInGroupState,
_ displayName: String?,
_ avatarImage: UIImage?) async -> RoomTimelineItemProtocol {
@@ -150,7 +140,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
return ImageRoomTimelineItem(id: message.id,
text: message.body,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
inGroupState: inGroupState,
isOutgoing: isOutgoing,
senderId: message.sender,
@@ -168,7 +157,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
private func buildNoticeTimelineItemFromMessage(_ message: MessageTimelineItem<NoticeMessageContent>,
_ isOutgoing: Bool,
_ showSenderDetails: Bool,
_ inGroupState: TimelineItemInGroupState,
_ displayName: String?,
_ avatarImage: UIImage?) async -> RoomTimelineItemProtocol {
@@ -179,7 +167,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
text: message.body,
attributedComponents: attributedComponents,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
inGroupState: inGroupState,
isOutgoing: isOutgoing,
senderId: message.sender,
@@ -191,7 +178,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
private func buildEmoteTimelineItemFromMessage(_ message: MessageTimelineItem<EmoteMessageContent>,
_ isOutgoing: Bool,
_ showSenderDetails: Bool,
_ inGroupState: TimelineItemInGroupState,
_ displayName: String?,
_ avatarImage: UIImage?) async -> RoomTimelineItemProtocol {
@@ -202,7 +188,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
text: message.body,
attributedComponents: attributedComponents,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
inGroupState: inGroupState,
isOutgoing: isOutgoing,
senderId: message.sender,
@@ -212,9 +197,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
reactions: aggregateReactions(message.reactions)))
}
// swiftlint:enable function_parameter_count
// swiftformat:enable function_parameter_count
private func aggregateReactions(_ reactions: [Reaction]) -> [AggregatedReaction] {
reactions.map { reaction in
let isHighlighted = false // reaction.details.contains(where: { $0.sender == userID })
@@ -19,6 +19,5 @@ import Foundation
@MainActor
protocol RoomTimelineItemFactoryProtocol {
func buildTimelineItemFor(eventItemProxy: EventTimelineItemProxy,
showSenderDetails: Bool,
inGroupState: TimelineItemInGroupState) async -> RoomTimelineItemProtocol
}
@@ -59,7 +59,7 @@ class UserSessionStore: UserSessionStoreProtocol {
case .success(let clientProxy):
return .success(UserSession(clientProxy: clientProxy,
mediaProvider: MediaProvider(clientProxy: clientProxy,
imageCache: ImageCache.default,
imageCache: .onlyInMemory,
backgroundTaskService: backgroundTaskService)))
case .failure(let error):
MXLog.error("Failed restoring login with error: \(error)")
@@ -77,7 +77,7 @@ class UserSessionStore: UserSessionStoreProtocol {
case .success(let clientProxy):
return .success(UserSession(clientProxy: clientProxy,
mediaProvider: MediaProvider(clientProxy: clientProxy,
imageCache: ImageCache.default,
imageCache: .onlyInMemory,
backgroundTaskService: backgroundTaskService)))
case .failure(let error):
MXLog.error("Failed creating user session with error: \(error)")