diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 6f6e6de7f..ab0c1440b 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -333,6 +333,8 @@ EBD6C79705B3DDB2F7E5F554 /* UserSessionStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1B52D0ABBA7091A991CAFE /* UserSessionStoreProtocol.swift */; }; EC280623A42904341363EAAF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 886A0A498FA01E8EDD451D05 /* Sentry */; }; EC4C31963E755EEC77BD778C /* AnalyticsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B362E695A7103C11F64B185 /* AnalyticsSettings.swift */; }; + ECA5A34628DC837E0024C8BE /* AvatarSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA5A34528DC837E0024C8BE /* AvatarSize.swift */; }; + ECA5A34828DC959F0024C8BE /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA5A34728DC959F0024C8BE /* ImageCache.swift */; }; EE4F5601356228FF72FC56B6 /* MockClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F40F48279322E504153AB0D /* MockClientProxy.swift */; }; EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */; }; EEC40663922856C65D1E0DF5 /* KeychainControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */; }; @@ -346,7 +348,7 @@ F656F92A63D3DC1978D79427 /* AnalyticsEvents in Frameworks */ = {isa = PBXBuildFile; productRef = 2A3F7BCCB18C15B30CCA39A9 /* AnalyticsEvents */; }; F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */; }; F7567DD6635434E8C563BF85 /* AnalyticsClientProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3B97591B2D3D4D67553506D /* AnalyticsClientProtocol.swift */; }; - F764BE976EAB76D63E7C1678 /* RoundedCorner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8023C7413A426FBF0A52B684 /* RoundedCorner.swift */; }; + F764BE976EAB76D63E7C1678 /* RoundedCornerShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8023C7413A426FBF0A52B684 /* RoundedCornerShape.swift */; }; F99FB21EFC6D99D247FE7CBE /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = D82E84F90358CC1118E6034B /* Introspect */; }; FA9C427FFB11B1AA2DCC5602 /* RoomProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */; }; FC6B7436C3A5B3D0565227D5 /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF05352F28D4E7336228E9F4 /* ActivityIndicatorView.swift */; }; @@ -604,7 +606,7 @@ 7E154FEA1E6FE964D3DF7859 /* fy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fy; path = fy.lproj/Localizable.strings; sourceTree = ""; }; 7E532D95330139D118A9BF88 /* BugReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportViewModel.swift; sourceTree = ""; }; 7FFCC48E7F701B6C24484593 /* WeakDictionaryKeyReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakDictionaryKeyReference.swift; sourceTree = ""; }; - 8023C7413A426FBF0A52B684 /* RoundedCorner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCorner.swift; sourceTree = ""; }; + 8023C7413A426FBF0A52B684 /* RoundedCornerShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCornerShape.swift; sourceTree = ""; }; 804F9B0FABE093C7284CD09B /* TimelineItemList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemList.swift; sourceTree = ""; }; 8140010A796DB2C7977B6643 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; 8166F121C79C7B62BF01D508 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = pt; path = pt.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -629,7 +631,7 @@ 8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineProvider.swift; sourceTree = ""; }; 8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = ""; }; - 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = ""; }; + 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = ""; }; 8F7D42E66E939B709C1EC390 /* MockRoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomSummaryProvider.swift; sourceTree = ""; }; 9010EE0CC913D095887EF36E /* OIDCService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCService.swift; sourceTree = ""; }; 90733775209F4D4D366A268F /* RootRouterType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouterType.swift; sourceTree = ""; }; @@ -795,6 +797,8 @@ E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIFont+AttributedStringBuilder.m"; sourceTree = ""; }; E9D059BFE329BE09B6D96A9F /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ro; path = ro.lproj/Localizable.stringsdict; sourceTree = ""; }; EBE5502760CF6CA2D7201883 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = ""; }; + ECA5A34528DC837E0024C8BE /* AvatarSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarSize.swift; sourceTree = ""; }; + ECA5A34728DC959F0024C8BE /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = ""; }; ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = ""; }; @@ -1009,7 +1013,7 @@ isa = PBXGroup; children = ( 0960A7F5C1B0B6679BDF26F9 /* ElementToggleStyle.swift */, - 8023C7413A426FBF0A52B684 /* RoundedCorner.swift */, + 8023C7413A426FBF0A52B684 /* RoundedCornerShape.swift */, ); path = Views; sourceTree = ""; @@ -1097,6 +1101,7 @@ A40C19719687984FD9478FBE /* Task.swift */, 287FC98AF2664EAD79C0D902 /* UIDevice.swift */, 227AC5D71A4CE43512062243 /* URL.swift */, + ECA5A34728DC959F0024C8BE /* ImageCache.swift */, ); path = Extensions; sourceTree = ""; @@ -1682,6 +1687,7 @@ 6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */, F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */, BB3073CCD77D906B330BC1D6 /* Tests.swift */, + ECA5A34528DC837E0024C8BE /* AvatarSize.swift */, 44BBB96FAA2F0D53C507396B /* Extensions */, 8F9A844EB44B6AD7CA18FD96 /* HTMLParsing */, 06501F0E978B2D5C92771DC7 /* Logging */, @@ -2419,6 +2425,7 @@ DF504B10A4918F971A57BEF2 /* PostHogAnalyticsClient.swift in Sources */, BF35062D06888FA80BD139FF /* Presentable.swift in Sources */, C76892321558E75101E68ED6 /* ReadableFrameModifier.swift in Sources */, + ECA5A34828DC959F0024C8BE /* ImageCache.swift in Sources */, 53B9C2240C2F5533246EE230 /* RectangleToastView.swift in Sources */, 00EA14F62DCEF62CDE4808D6 /* RedactedRoomTimelineItem.swift in Sources */, 13853973A5E24374FCEDE8A3 /* RedactedRoomTimelineView.swift in Sources */, @@ -2449,7 +2456,7 @@ CF82143AA4A4F7BD11D22946 /* RoomTimelineViewProvider.swift in Sources */, 7F19E97E7985F518C9018B83 /* RootRouter.swift in Sources */, 2C0CE61E5DC177938618E0B1 /* RootRouterType.swift in Sources */, - F764BE976EAB76D63E7C1678 /* RoundedCorner.swift in Sources */, + F764BE976EAB76D63E7C1678 /* RoundedCornerShape.swift in Sources */, 462813B93C39DF93B1249403 /* RoundedToastView.swift in Sources */, CC736DA1AA8F8B9FD8785009 /* ScreenshotDetector.swift in Sources */, 1281625B25371BE53D36CB3A /* SeparatorRoomTimelineItem.swift in Sources */, @@ -2527,6 +2534,7 @@ 15D1F9C415D9C921643BA82E /* UserIndicatorRequest.swift in Sources */, C052A8CDC7A8E7A2D906674F /* UserIndicatorStore.swift in Sources */, 80E04BE80A89A78FBB4863BB /* UserIndicatorViewPresentable.swift in Sources */, + ECA5A34628DC837E0024C8BE /* AvatarSize.swift in Sources */, 9CCC77C31CB399661A034739 /* UserProperties+Element.swift in Sources */, 8AB8ED1051216546CB35FA0E /* UserSession.swift in Sources */, 978BB24F2A5D31EE59EEC249 /* UserSessionProtocol.swift in Sources */, diff --git a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings index 5c38f923c..fd08b9612 100644 --- a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings +++ b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings @@ -21,8 +21,6 @@ "session_verification_screen_emojis_title" = "Lets check if these"; "session_verification_screen_emojis_message" = "Open Element on one of your other sessions to compare."; -"home_screen_all_chats" = "All Chats"; - // MARK: - Authentication "authentication_login_title" = "Welcome back!"; diff --git a/ElementX/Sources/AppCoordinator.swift b/ElementX/Sources/AppCoordinator.swift index d0f03c941..844012e8c 100644 --- a/ElementX/Sources/AppCoordinator.swift +++ b/ElementX/Sources/AppCoordinator.swift @@ -130,7 +130,7 @@ class AppCoordinator: Coordinator { MXLog.configure(loggerConfiguration) } - // swiftlint:disable:next cyclomatic_complexity function_body_length + // swiftlint:disable:next cyclomatic_complexity private func setupStateMachine() { stateMachine.addTransitionHandler { [weak self] context in guard let self = self else { return } @@ -147,15 +147,14 @@ class AppCoordinator: Coordinator { case (.restoringSession, .failedRestoringSession, .signedOut): self.hideLoadingIndicator() self.showLoginErrorToast() - self.startAuthentication() case (.restoringSession, .succeededRestoringSession, .homeScreen): self.hideLoadingIndicator() self.presentHomeScreen() case(_, _, .roomScreen(let roomId)): self.presentRoomWithIdentifier(roomId) - case(.roomScreen, .dismissedRoomScreen, .homeScreen): - self.tearDownDismissedRoomScreen() + case(.roomScreen(let roomId), .dismissedRoomScreen, .homeScreen): + self.tearDownDismissedRoomScreen(roomId) case (_, .signOut, .signingOut): self.showLoadingIndicator() @@ -239,13 +238,11 @@ class AppCoordinator: Coordinator { self.remove(childCoordinator: coordinator) self.stateMachine.processEvent(.succeededSigningIn) case .clearAllData: - self.confirmClearAllData { - // clear user data - self.userSessionStore.logout(userSession: self.userSession) - self.userSession = nil - self.remove(childCoordinator: coordinator) - self.startAuthentication() - } + // clear user data + self.userSessionStore.logout(userSession: self.userSession) + self.userSession = nil + self.remove(childCoordinator: coordinator) + self.startAuthentication() } } @@ -310,7 +307,7 @@ class AppCoordinator: Coordinator { case .verifySession: self.stateMachine.processEvent(.showSessionVerificationScreen) case .signOut: - self.confirmSignOut() + self.stateMachine.processEvent(.signOut) } } @@ -368,7 +365,7 @@ class AppCoordinator: Coordinator { let parameters = RoomScreenCoordinatorParameters(timelineController: timelineController, roomName: roomProxy.displayName ?? roomProxy.name, - roomAvatar: userSession.mediaProvider.imageFromURLString(roomProxy.avatarURL, size: MediaProviderDefaultAvatarSize)) + roomAvatar: userSession.mediaProvider.imageFromURLString(roomProxy.avatarURL, avatarSize: .room(on: .timeline))) let coordinator = RoomScreenCoordinator(parameters: parameters) add(childCoordinator: coordinator) @@ -378,7 +375,7 @@ class AppCoordinator: Coordinator { } } - private func tearDownDismissedRoomScreen() { + private func tearDownDismissedRoomScreen(_ roomId: String) { guard let coordinator = childCoordinators.last as? RoomScreenCoordinator else { fatalError("Invalid coordinator hierarchy: \(childCoordinators)") } @@ -438,32 +435,6 @@ class AppCoordinator: Coordinator { navigationRouter.present(alert, animated: true) } - private func confirmSignOut() { - let alert = UIAlertController(title: ElementL10n.actionSignOut, - message: ElementL10n.actionSignOutConfirmationSimple, - preferredStyle: .alert) - - alert.addAction(UIAlertAction(title: ElementL10n.actionCancel, style: .cancel)) - alert.addAction(UIAlertAction(title: ElementL10n.actionSignOut, style: .destructive) { [weak self] _ in - self?.stateMachine.processEvent(.signOut) - }) - - navigationRouter.present(alert, animated: true) - } - - /// Shows a confirmation to clear all data, and proceeds to do so if the user confirms. - private func confirmClearAllData(_ confirmed: @escaping () -> Void) { - let alert = UIAlertController(title: ElementL10n.softLogoutClearDataDialogTitle, - message: ElementL10n.softLogoutClearDataDialogContent, - preferredStyle: .alert) - alert.addAction(UIAlertAction(title: ElementL10n.actionCancel, style: .cancel, handler: nil)) - alert.addAction(UIAlertAction(title: ElementL10n.actionSignOut, style: .destructive) { _ in - confirmed() - }) - - navigationRouter.present(alert, animated: true) - } - private func processScreenshotDetection(image: UIImage?, error: Error?) { MXLog.debug("Detected screenshot: \(String(describing: image)), error: \(String(describing: error))") diff --git a/ElementX/Sources/Generated/Strings+Untranslated.swift b/ElementX/Sources/Generated/Strings+Untranslated.swift index dbfcf0461..bc41d2689 100644 --- a/ElementX/Sources/Generated/Strings+Untranslated.swift +++ b/ElementX/Sources/Generated/Strings+Untranslated.swift @@ -22,8 +22,6 @@ extension ElementL10n { public static let authenticationServerInfoMatrixDescription = ElementL10n.tr("Untranslated", "authentication_server_info_matrix_description") /// Choose your server to store your data public static let authenticationServerInfoTitle = ElementL10n.tr("Untranslated", "authentication_server_info_title") - /// All Chats - public static let homeScreenAllChats = ElementL10n.tr("Untranslated", "home_screen_all_chats") /// Mobile public static let loginMobileDevice = ElementL10n.tr("Untranslated", "login_mobile_device") /// Tablet diff --git a/ElementX/Sources/Other/AvatarSize.swift b/ElementX/Sources/Other/AvatarSize.swift new file mode 100644 index 000000000..3f308c253 --- /dev/null +++ b/ElementX/Sources/Other/AvatarSize.swift @@ -0,0 +1,83 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import UIKit + +enum AvatarSize { + case user(on: UserAvatarSizeOnScreen) + case room(on: RoomAvatarSizeOnScreen) + // custom + case custom(CGFloat) + + /// Value in UIKit points + var value: CGFloat { + switch self { + case .user(let screen): + return screen.value + case .room(let screen): + return screen.value + case .custom(let val): + return val + } + } + + /// Value in pixels by using the scale of the main screen + var scaledValue: CGFloat { + value * UIScreen.main.scale + } +} + +enum UserAvatarSizeOnScreen { + case timeline + case home + case settings + + var value: CGFloat { + switch self { + case .timeline: + return 32 + case .home: + return 32 + case .settings: + return 60 + } + } +} + +enum RoomAvatarSizeOnScreen { + case timeline + case home + + var value: CGFloat { + switch self { + case .timeline: + return 32 + case .home: + return 44 + } + } +} + +extension AvatarSize { + var size: CGSize { + CGSize(width: value, height: value) + } + + var scaledSize: CGSize { + CGSize(width: scaledValue, height: scaledValue) + } +} diff --git a/ElementX/Sources/Other/Extensions/ImageCache.swift b/ElementX/Sources/Other/Extensions/ImageCache.swift new file mode 100644 index 000000000..503b917ce --- /dev/null +++ b/ElementX/Sources/Other/Extensions/ImageCache.swift @@ -0,0 +1,26 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Kingfisher + +extension ImageCache { + static var onlyInMemory: ImageCache { + let result = ImageCache.default + result.diskStorage.config.sizeLimit = 1 + return result + } +} diff --git a/ElementX/Sources/Other/SwiftUI/Views/RoundedCorner.swift b/ElementX/Sources/Other/SwiftUI/Views/RoundedCornerShape.swift similarity index 88% rename from ElementX/Sources/Other/SwiftUI/Views/RoundedCorner.swift rename to ElementX/Sources/Other/SwiftUI/Views/RoundedCornerShape.swift index a4753c984..7729a400e 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/RoundedCorner.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/RoundedCornerShape.swift @@ -16,7 +16,7 @@ import SwiftUI -struct RoundedCorner: Shape { +struct RoundedCornerShape: Shape { let radius: CGFloat let corners: UIRectCorner @@ -39,10 +39,10 @@ struct RoundedCorner: Shape { extension View { func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { - clipShape(RoundedCorner(radius: radius, corners: corners)) + clipShape(RoundedCornerShape(radius: radius, corners: corners)) } func cornerRadius(_ radius: CGFloat, inGroupState: TimelineItemInGroupState) -> some View { - clipShape(RoundedCorner(radius: radius, inGroupState: inGroupState)) + clipShape(RoundedCornerShape(radius: radius, inGroupState: inGroupState)) } } diff --git a/ElementX/Sources/Screens/Authentication/SoftLogout/View/SoftLogoutScreen.swift b/ElementX/Sources/Screens/Authentication/SoftLogout/View/SoftLogoutScreen.swift index 304ceb94f..0dd85946e 100644 --- a/ElementX/Sources/Screens/Authentication/SoftLogout/View/SoftLogoutScreen.swift +++ b/ElementX/Sources/Screens/Authentication/SoftLogout/View/SoftLogoutScreen.swift @@ -21,6 +21,8 @@ struct SoftLogoutScreen: View { // MARK: Private + @State private var showingClearDataConfirmation = false + /// The focus state of the password text field. @FocusState private var isPasswordFocused: Bool @@ -150,6 +152,14 @@ struct SoftLogoutScreen: View { } .buttonStyle(.elementAction(.xLarge, color: .element.alert)) .accessibilityIdentifier("clearDataButton") + .alert(ElementL10n.softLogoutClearDataDialogTitle, + isPresented: $showingClearDataConfirmation) { + Button(ElementL10n.actionSignOut, + role: .destructive, + action: clearData) + } message: { + Text(ElementL10n.softLogoutClearDataDialogContent) + } } } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index 62ab0ed99..dd4dc9617 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -39,6 +39,8 @@ enum HomeScreenViewAction { } struct HomeScreenViewState: BindableState { + var userID: String + var userDisplayName: String? var userAvatar: UIImage? var showSessionVerificationBanner = false diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 15deeff31..0bb5c8370 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -33,7 +33,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol roomSummaryProvider = userSession.clientProxy.roomSummaryProvider self.attributedStringBuilder = attributedStringBuilder - super.init(initialViewState: HomeScreenViewState()) + super.init(initialViewState: HomeScreenViewState(userID: userSession.userID)) userSession.callbacks .receive(on: DispatchQueue.main) @@ -61,13 +61,19 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol Task { if case let .success(userAvatarURLString) = await userSession.clientProxy.loadUserAvatarURLString() { - if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURLString(userAvatarURLString, size: MediaProviderDefaultAvatarSize) { + if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURLString(userAvatarURLString, avatarSize: .user(on: .home)) { state.userAvatar = avatar } } - + await updateRooms() } + + Task { + if case let .success(userDisplayName) = await userSession.clientProxy.loadUserDisplayName() { + state.userDisplayName = userDisplayName + } + } } // MARK: - Public @@ -101,7 +107,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol if let avatarURLString = summary.avatarURLString { Task { - if case let .success(image) = await userSession.mediaProvider.loadImageFromURLString(avatarURLString, size: MediaProviderDefaultAvatarSize) { + if case let .success(image) = await userSession.mediaProvider.loadImageFromURLString(avatarURLString, avatarSize: .room(on: .home)) { if let index = state.rooms.firstIndex(of: room) { room.avatar = image state.rooms[index] = room @@ -116,7 +122,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol var rooms = [HomeScreenRoom]() for summary in self.roomSummaryProvider.roomSummaries { - let avatarImage = await self.userSession.mediaProvider.imageFromURLString(summary.avatarURLString, size: MediaProviderDefaultAvatarSize) + let avatarImage = await self.userSession.mediaProvider.imageFromURLString(summary.avatarURLString, avatarSize: .room(on: .home)) var timestamp: String? if let lastMessageTimestamp = summary.lastMessageTimestamp { diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift index 14e4b32bb..3062d586d 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift @@ -17,6 +17,7 @@ import SwiftUI struct HomeScreen: View { + @State private var showingLogoutConfirmation = false @ObservedObject var context: HomeScreenViewModel.Context // MARK: Views @@ -50,7 +51,7 @@ struct HomeScreen: View { .transition(.slide) .animation(.elementDefault, value: context.viewState.showSessionVerificationBanner) .ignoresSafeArea(.all, edges: .bottom) - .navigationTitle(ElementL10n.homeScreenAllChats) + .navigationTitle(ElementL10n.allChats) .toolbar { ToolbarItem(placement: .navigationBarLeading) { userMenuButton @@ -75,7 +76,9 @@ struct HomeScreen: View { } } Section { - Button(role: .destructive, action: signOut) { + Button(role: .destructive) { + showingLogoutConfirmation = true + } label: { Label(ElementL10n.actionSignOut, systemImage: "rectangle.portrait.and.arrow.right") } } @@ -84,23 +87,33 @@ struct HomeScreen: View { .animation(.elementDefault, value: context.viewState.userAvatar) .transition(.opacity) } + .alert(ElementL10n.actionSignOut, + isPresented: $showingLogoutConfirmation) { + Button(ElementL10n.actionSignOut, + role: .destructive, + action: signOut) + } message: { + Text(ElementL10n.actionSignOutConfirmationSimple) + } } @ViewBuilder private var userAvatarImageView: some View { userAvatarImage - .resizable() - .scaledToFill() - .frame(width: 32, height: 32, alignment: .center) + .frame(width: AvatarSize.user(on: .home).value, height: AvatarSize.user(on: .home).value, alignment: .center) .clipShape(Circle()) .accessibilityIdentifier("userAvatarImage") } - private var userAvatarImage: Image { + @ViewBuilder + private var userAvatarImage: some View { if let avatar = context.viewState.userAvatar { - return Image(uiImage: avatar) + Image(uiImage: avatar) + .resizable() + .scaledToFill() } else { - return .empty + PlaceholderAvatarImage(text: context.viewState.userDisplayName ?? context.viewState.userID, + contentId: context.viewState.userID) } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift index f33920a70..09440d84b 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift @@ -17,7 +17,7 @@ import SwiftUI struct HomeScreenRoomCell: View { - @ScaledMetric private var avatarSize = 44.0 + @ScaledMetric private var avatarSize = AvatarSize.room(on: .home).value let room: HomeScreenRoom let context: HomeScreenViewModel.Context diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift index 69af08ffc..73e3224ac 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift @@ -40,6 +40,7 @@ enum RoomScreenViewAction { case sendMessage case sendReaction(key: String, eventID: String) case cancelReply + case viewDisappeared } struct RoomScreenViewState: BindableState { diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index a6aa3b0f3..cc2e12743 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -89,6 +89,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol MXLog.warning("React with \(key) failed. Not implemented.") case .cancelReply: state.composerMode = .default + case .viewDisappeared: + cancellables.forEach { $0.cancel() } + cancellables.removeAll() + state.contextMenuBuilder = nil } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift index b8026de35..628e49ec3 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift @@ -42,7 +42,7 @@ struct RoomHeaderView: View { .accessibilityIdentifier("encryptionBadgeIcon") } } - .frame(width: 32.0, height: 32.0) + .frame(width: AvatarSize.room(on: .timeline).value, height: AvatarSize.room(on: .timeline).value) } @ViewBuilder private var roomAvatarImage: some View { diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index f6db3ce66..3c47522a5 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -41,6 +41,9 @@ struct RoomScreen: View { } } .alert(item: $context.alertInfo) { $0.alert } + .onDisappear { + context.send(viewAction: .viewDisappeared) + } } private func sendMessage() { diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift index ec6d9b2c7..27cfa6e20 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift @@ -144,7 +144,7 @@ struct TimelineItemBubbledStylerView: View { .background(Color.element.systemGray6) // Demo time! .cornerRadius(12, inGroupState: timelineItem.inGroupState) // Demo time! // .overlay( -// RoundedCorner(radius: 18, inGroupState: timelineItem.inGroupState) +// RoundedCornerShape(radius: 18, inGroupState: timelineItem.inGroupState) // .stroke(Color.element.systemGray5) // ) } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift index 7099ef7e2..2571124c7 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift @@ -58,7 +58,6 @@ struct EmoteRoomTimelineView_Previews: PreviewProvider { EmoteRoomTimelineItem(id: UUID().uuidString, text: text, timestamp: timestamp, - shouldShowSenderDetails: true, inGroupState: .single, isOutgoing: false, senderId: senderId) diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift index ab6a68137..494128a9c 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift @@ -26,25 +26,13 @@ struct FormattedBodyText: View { VStack(alignment: .leading, spacing: 8.0) { ForEach(attributedComponents, id: \.self) { component in if component.isBlockquote { - if isOutgoing { - Text(component.attributedString) - .fixedSize(horizontal: false, vertical: true) - .foregroundColor(.element.primaryContent) - .padding(EdgeInsets(top: 6, leading: 12, bottom: 6, trailing: 12)) - .clipped() - .background(Color.element.systemGray4) - .cornerRadius(13) - } else { - Text(component.attributedString) - .fixedSize(horizontal: false, vertical: true) - .foregroundColor(.element.primaryContent) - .padding(EdgeInsets(top: 8, leading: 12, bottom: 8, trailing: 12)) - .clipped() - .overlay( - RoundedRectangle(cornerRadius: 13) - .stroke(Color.element.systemGray5) - ) - } + Text(component.attributedString.mergingAttributes(blockquoteAttributes)) + .fixedSize(horizontal: false, vertical: true) + .foregroundColor(.element.primaryContent) + .padding(EdgeInsets(top: 6, leading: 12, bottom: 6, trailing: 12)) + .clipped() + .background(Color.element.systemGray4) + .cornerRadius(13) } else { Text(component.attributedString) .fixedSize(horizontal: false, vertical: true) @@ -54,6 +42,12 @@ struct FormattedBodyText: View { } .tint(.element.accent) } + + private var blockquoteAttributes: AttributeContainer { + var container = AttributeContainer() + container.font = .element.caption1 + return container + } } extension FormattedBodyText { diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift index 5acf8a1a8..43ffba9fe 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift @@ -62,7 +62,6 @@ struct ImageRoomTimelineView_Previews: PreviewProvider { ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: UUID().uuidString, text: "Some image", timestamp: "Now", - shouldShowSenderDetails: false, inGroupState: .single, isOutgoing: false, senderId: "Bob", @@ -72,7 +71,6 @@ struct ImageRoomTimelineView_Previews: PreviewProvider { ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: UUID().uuidString, text: "Some other image", timestamp: "Now", - shouldShowSenderDetails: false, inGroupState: .single, isOutgoing: false, senderId: "Bob", @@ -82,7 +80,6 @@ struct ImageRoomTimelineView_Previews: PreviewProvider { ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: UUID().uuidString, text: "Blurhashed image", timestamp: "Now", - shouldShowSenderDetails: false, inGroupState: .single, isOutgoing: false, senderId: "Bob", diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift index fa4cf27b0..e132045c5 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift @@ -59,7 +59,6 @@ struct NoticeRoomTimelineView_Previews: PreviewProvider { NoticeRoomTimelineItem(id: UUID().uuidString, text: text, timestamp: timestamp, - shouldShowSenderDetails: true, inGroupState: .single, isOutgoing: false, senderId: senderId) diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/PlaceholderAvatarImage.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/PlaceholderAvatarImage.swift index 73b11f309..e7bd59ed9 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/PlaceholderAvatarImage.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/PlaceholderAvatarImage.swift @@ -25,7 +25,7 @@ struct PlaceholderAvatarImage: View { bgColor Text(textForImage) .padding(4) - .foregroundColor(.element.background) + .foregroundColor(.white) .font(.title2.bold()) } .aspectRatio(1, contentMode: .fill) diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/RedactedRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/RedactedRoomTimelineView.swift index 25468aa3e..6d9f80e46 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/RedactedRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/RedactedRoomTimelineView.swift @@ -51,7 +51,6 @@ struct RedactedRoomTimelineView_Previews: PreviewProvider { RedactedRoomTimelineItem(id: UUID().uuidString, text: text, timestamp: timestamp, - shouldShowSenderDetails: true, inGroupState: .single, isOutgoing: false, senderId: senderId) diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift index 9e31ae812..47c4b3e36 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift @@ -43,26 +43,22 @@ struct TextRoomTimelineView_Previews: PreviewProvider { VStack(alignment: .leading, spacing: 20.0) { TextRoomTimelineView(timelineItem: itemWith(text: "Short loin ground round tongue hamburger, fatback salami shoulder. Beef turkey sausage kielbasa strip steak. Alcatra capicola pig tail pancetta chislic.", timestamp: "Now", - shouldShowSenderDetails: true, isOutgoing: false, senderId: "Bob")) TextRoomTimelineView(timelineItem: itemWith(text: "Some other text", timestamp: "Later", - shouldShowSenderDetails: true, isOutgoing: true, senderId: "Anne")) TextRoomTimelineView(timelineItem: itemWith(text: "Short loin ground round tongue hamburger, fatback salami shoulder. Beef turkey sausage kielbasa strip steak. Alcatra capicola pig tail pancetta chislic.", timestamp: "Now", - shouldShowSenderDetails: true, isOutgoing: false, senderId: "Bob")) .timelineStyle(.plain) TextRoomTimelineView(timelineItem: itemWith(text: "Some other text", timestamp: "Later", - shouldShowSenderDetails: true, isOutgoing: true, senderId: "Anne")) .timelineStyle(.plain) @@ -70,11 +66,10 @@ struct TextRoomTimelineView_Previews: PreviewProvider { .padding(.horizontal, 8) } - private static func itemWith(text: String, timestamp: String, shouldShowSenderDetails: Bool, isOutgoing: Bool, senderId: String) -> TextRoomTimelineItem { + private static func itemWith(text: String, timestamp: String, isOutgoing: Bool, senderId: String) -> TextRoomTimelineItem { TextRoomTimelineItem(id: UUID().uuidString, text: text, timestamp: timestamp, - shouldShowSenderDetails: shouldShowSenderDetails, inGroupState: .single, isOutgoing: isOutgoing, senderId: senderId) diff --git a/ElementX/Sources/Screens/RoomScreen/View/TimelineSenderAvatarView.swift b/ElementX/Sources/Screens/RoomScreen/View/TimelineSenderAvatarView.swift index 88c6bb505..e605a7944 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/TimelineSenderAvatarView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/TimelineSenderAvatarView.swift @@ -20,7 +20,7 @@ import SwiftUI struct TimelineSenderAvatarView: View { let timelineItem: EventBasedTimelineItemProtocol - @ScaledMetric private var avatarSize = 32 + @ScaledMetric private var avatarSize = AvatarSize.user(on: .timeline).value var body: some View { ZStack(alignment: .center) { diff --git a/ElementX/Sources/Screens/Settings/SettingsCoordinator.swift b/ElementX/Sources/Screens/Settings/SettingsCoordinator.swift index b9873b4a2..0578d18a6 100644 --- a/ElementX/Sources/Screens/Settings/SettingsCoordinator.swift +++ b/ElementX/Sources/Screens/Settings/SettingsCoordinator.swift @@ -73,7 +73,7 @@ final class SettingsCoordinator: Coordinator, Presentable { case .crash: self.parameters.bugReportService.crash() case .logout: - self.confirmSignOut() + self.callback?(.logout) } } } @@ -118,19 +118,6 @@ final class SettingsCoordinator: Coordinator, Presentable { } } - private func confirmSignOut() { - let alert = UIAlertController(title: ElementL10n.actionSignOut, - message: ElementL10n.actionSignOutConfirmationSimple, - preferredStyle: .alert) - - alert.addAction(UIAlertAction(title: ElementL10n.actionCancel, style: .cancel)) - alert.addAction(UIAlertAction(title: ElementL10n.actionSignOut, style: .destructive) { [weak self] _ in - self?.callback?(.logout) - }) - - navigationRouter.present(alert, animated: true) - } - /// Show an activity indicator whilst loading. /// - Parameters: /// - label: The label to show on the indicator. diff --git a/ElementX/Sources/Screens/Settings/SettingsViewModel.swift b/ElementX/Sources/Screens/Settings/SettingsViewModel.swift index db407a374..1e88ebd34 100644 --- a/ElementX/Sources/Screens/Settings/SettingsViewModel.swift +++ b/ElementX/Sources/Screens/Settings/SettingsViewModel.swift @@ -40,11 +40,13 @@ class SettingsViewModel: SettingsViewModelType, SettingsViewModelProtocol { Task { if case let .success(userAvatarURLString) = await userSession.clientProxy.loadUserAvatarURLString() { - if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURLString(userAvatarURLString, size: MediaProviderDefaultAvatarSize) { + if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURLString(userAvatarURLString, avatarSize: .user(on: .settings)) { state.userAvatar = avatar } } + } + Task { if case let .success(userDisplayName) = await self.userSession.clientProxy.loadUserDisplayName() { state.userDisplayName = userDisplayName } diff --git a/ElementX/Sources/Screens/Settings/View/SettingsScreen.swift b/ElementX/Sources/Screens/Settings/View/SettingsScreen.swift index a793fb176..1ef8508f3 100644 --- a/ElementX/Sources/Screens/Settings/View/SettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/View/SettingsScreen.swift @@ -19,10 +19,11 @@ import SwiftUI struct SettingsScreen: View { // MARK: Private + @State private var showingLogoutConfirmation = false @Environment(\.colorScheme) private var colorScheme @ObservedObject private var settings = ElementSettings.shared - @ScaledMetric private var avatarSize = 60.0 + @ScaledMetric private var avatarSize = AvatarSize.user(on: .settings).value @ScaledMetric private var menuIconSize = 30.0 private let listRowInsets = EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16) @@ -169,7 +170,7 @@ struct SettingsScreen: View { private var logoutSection: some View { Section { - Button(action: logout) { + Button { showingLogoutConfirmation = true } label: { HStack { Image(systemName: "rectangle.portrait.and.arrow.right") .foregroundColor(.element.systemGray) @@ -186,6 +187,14 @@ struct SettingsScreen: View { .listRowInsets(listRowInsets) .foregroundColor(.element.primaryContent) .accessibilityIdentifier("logoutButton") + .alert(ElementL10n.actionSignOut, + isPresented: $showingLogoutConfirmation) { + Button(ElementL10n.actionSignOut, + role: .destructive, + action: logout) + } message: { + Text(ElementL10n.actionSignOutConfirmationSimple) + } } footer: { versionText .frame(maxWidth: .infinity) @@ -194,12 +203,10 @@ struct SettingsScreen: View { private var closeButton: some View { Button(action: close) { - HStack { - Image(systemName: "xmark") - .font(.title3.bold()) - .foregroundColor(.element.secondaryContent) - .padding(4) - } + Image(systemName: "xmark") + .font(.title3.bold()) + .foregroundColor(.element.secondaryContent) + .padding(4) } .accessibilityIdentifier("closeButton") } diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index de233a891..d0da6906d 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -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 { await Task.dispatch(on: .global()) { do { diff --git a/ElementX/Sources/Services/Media/MediaProvider.swift b/ElementX/Sources/Services/Media/MediaProvider.swift index 780357ee2..82c64fcc0 100644 --- a/ElementX/Sources/Services/Media/MediaProvider.swift +++ b/ElementX/Sources/Services/Media/MediaProvider.swift @@ -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 { - await loadImageFromSource(MediaSource(source: clientProxy.mediaSourceForURLString(urlString)), size: size) + func loadImageFromURLString(_ urlString: String, avatarSize: AvatarSize?) async -> Result { + await loadImageFromSource(MediaSource(source: clientProxy.mediaSourceForURLString(urlString)), avatarSize: avatarSize) } - func loadImageFromSource(_ source: MediaSource, size: CGSize?) async -> Result { - if let image = imageFromSource(source, size: size) { + func loadImageFromSource(_ source: MediaSource, avatarSize: AvatarSize?) async -> Result { + 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 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 } diff --git a/ElementX/Sources/Services/Media/MediaProviderProtocol.swift b/ElementX/Sources/Services/Media/MediaProviderProtocol.swift index 512aae5e3..cad940ab2 100644 --- a/ElementX/Sources/Services/Media/MediaProviderProtocol.swift +++ b/ElementX/Sources/Services/Media/MediaProviderProtocol.swift @@ -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 + @discardableResult func loadImageFromSource(_ source: MediaSource, avatarSize: AvatarSize?) async -> Result - func imageFromURLString(_ urlString: String?, size: CGSize?) -> UIImage? + func imageFromURLString(_ urlString: String?, avatarSize: AvatarSize?) -> UIImage? - @discardableResult func loadImageFromURLString(_ urlString: String, size: CGSize?) async -> Result + @discardableResult func loadImageFromURLString(_ urlString: String, avatarSize: AvatarSize?) async -> Result } extension MediaProviderProtocol { func imageFromSource(_ source: MediaSource?) -> UIImage? { - imageFromSource(source, size: nil) + imageFromSource(source, avatarSize: nil) } @discardableResult func loadImageFromSource(_ source: MediaSource) async -> Result { - 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 { - await loadImageFromURLString(urlString, size: nil) + await loadImageFromURLString(urlString, avatarSize: nil) } } diff --git a/ElementX/Sources/Services/Media/MockMediaProvider.swift b/ElementX/Sources/Services/Media/MockMediaProvider.swift index 8bd68b887..1487989cf 100644 --- a/ElementX/Sources/Services/Media/MockMediaProvider.swift +++ b/ElementX/Sources/Services/Media/MockMediaProvider.swift @@ -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 { + func loadImageFromSource(_ source: MediaSource, avatarSize: AvatarSize?) async -> Result { .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 { + func loadImageFromURLString(_ urlString: String, avatarSize: AvatarSize?) async -> Result { .failure(.failedRetrievingImage) } } diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index a4c60352e..5c27fde2c 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -42,7 +42,7 @@ class RoomProxy: RoomProxyProtocol { }() deinit { - #warning("Should any timeline listeners be removed??") + room.removeTimeline() } init(slidingSyncRoom: SlidingSyncRoomProtocol, diff --git a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift index 1ede403c9..2bef449b5 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift @@ -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), diff --git a/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift b/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift index 2ded3a052..4fdcfd145 100644 --- a/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift @@ -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: "", diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift index 900dd057d..c10947583 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift @@ -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 { diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift index b772050db..c9fb688fc 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift @@ -59,3 +59,9 @@ protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol { var properties: RoomTimelineItemProperties { get } } + +extension EventBasedTimelineItemProtocol { + var shouldShowSenderDetails: Bool { + inGroupState.shouldShowSenderDetails + } +} diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/EmoteRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/EmoteRoomTimelineItem.swift index 436ad8ec4..7312d59af 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/EmoteRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/EmoteRoomTimelineItem.swift @@ -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 diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift index f96884e74..ea8d4f796 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift @@ -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 diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/NoticeRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/NoticeRoomTimelineItem.swift index 1b81e48a9..bbcc48036 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/NoticeRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/NoticeRoomTimelineItem.swift @@ -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 diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/RedactedRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/RedactedRoomTimelineItem.swift index fe43ed458..71eb9749b 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/RedactedRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/RedactedRoomTimelineItem.swift @@ -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 diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift index f395ba43f..e9098f27b 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift @@ -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 diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index 8c6a6725c..f73ce9466 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -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, _ 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, _ 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, _ 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, _ 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 }) diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactoryProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactoryProtocol.swift index eb8a8cf9c..2abab38f8 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactoryProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactoryProtocol.swift @@ -19,6 +19,5 @@ import Foundation @MainActor protocol RoomTimelineItemFactoryProtocol { func buildTimelineItemFor(eventItemProxy: EventTimelineItemProxy, - showSenderDetails: Bool, inGroupState: TimelineItemInGroupState) async -> RoomTimelineItemProtocol } diff --git a/ElementX/Sources/Services/UserSessionStore/UserSessionStore.swift b/ElementX/Sources/Services/UserSessionStore/UserSessionStore.swift index f40f1b907..25404b683 100644 --- a/ElementX/Sources/Services/UserSessionStore/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSessionStore/UserSessionStore.swift @@ -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)") diff --git a/ElementX/Sources/UITestScreenIdentifier.swift b/ElementX/Sources/UITestScreenIdentifier.swift index 1f5652b3c..35480ec7d 100644 --- a/ElementX/Sources/UITestScreenIdentifier.swift +++ b/ElementX/Sources/UITestScreenIdentifier.swift @@ -25,6 +25,7 @@ enum UITestScreenIdentifier: String { case analyticsPrompt case simpleRegular case simpleUpgrade + case home case settings case bugReport case bugReportWithScreenshot diff --git a/ElementX/Sources/UITestsAppCoordinator.swift b/ElementX/Sources/UITestsAppCoordinator.swift index fc9ad7b85..f771ea33f 100644 --- a/ElementX/Sources/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITestsAppCoordinator.swift @@ -27,6 +27,7 @@ class UITestsAppCoordinator: Coordinator { init() { mainNavigationController = ElementNavigationController() + mainNavigationController.navigationBar.prefersLargeTitles = true navigationRouter = NavigationRouter(navigationController: mainNavigationController) window = UIWindow(frame: UIScreen.main.bounds) @@ -94,6 +95,10 @@ class MockScreen: Identifiable { return TemplateCoordinator(parameters: .init(promptType: .regular)) case .simpleUpgrade: return TemplateCoordinator(parameters: .init(promptType: .upgrade)) + case .home: + let session = MockUserSession(clientProxy: MockClientProxy(userIdentifier: "@mock:matrix.org"), + mediaProvider: MockMediaProvider()) + return HomeScreenCoordinator(parameters: .init(userSession: session, attributedStringBuilder: AttributedStringBuilder())) case .settings: return SettingsCoordinator(parameters: .init(navigationRouter: navigationRouter, userSession: MockUserSession(clientProxy: MockClientProxy(userIdentifier: "@mock:client.com"), diff --git a/UITests/Sources/HomeScreenUITests.swift b/UITests/Sources/HomeScreenUITests.swift index ab2bedd41..4da91b726 100644 --- a/UITests/Sources/HomeScreenUITests.swift +++ b/UITests/Sources/HomeScreenUITests.swift @@ -15,3 +15,14 @@ // import XCTest + +class HomeScreenUITests: XCTestCase { + func testInitialStateComponents() { + let app = Application.launch() + app.goToScreenWithIdentifier(.home) + + XCTAssert(app.navigationBars[ElementL10n.allChats].exists) + + app.assertScreenshot(.home) + } +} diff --git a/UITests/Sources/__Snapshots__/Application/15-5-de-DE-iPad-9th-generation.home.png b/UITests/Sources/__Snapshots__/Application/15-5-de-DE-iPad-9th-generation.home.png new file mode 100644 index 000000000..c0168795e --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/15-5-de-DE-iPad-9th-generation.home.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:988492045b2979e6a0cdb0724717f03ee342dff29c7bed991acc2a070a8b7af6 +size 111569 diff --git a/UITests/Sources/__Snapshots__/Application/15-5-de-DE-iPhone-13-Pro-Max.home.png b/UITests/Sources/__Snapshots__/Application/15-5-de-DE-iPhone-13-Pro-Max.home.png new file mode 100644 index 000000000..e6d0e316c --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/15-5-de-DE-iPhone-13-Pro-Max.home.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fd728a71eeb041a99cdfe1193ce11408f1c4abc5d01ebef22627c513f31071d +size 123201 diff --git a/UITests/Sources/__Snapshots__/Application/15-5-en-GB-iPad-9th-generation.home.png b/UITests/Sources/__Snapshots__/Application/15-5-en-GB-iPad-9th-generation.home.png new file mode 100644 index 000000000..514bcca16 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/15-5-en-GB-iPad-9th-generation.home.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f07e47863e9972064b8fabda201eb097f2cc4288895e44795f6275f08f2cec6 +size 111639 diff --git a/UITests/Sources/__Snapshots__/Application/15-5-en-GB-iPhone-13-Pro-Max.home.png b/UITests/Sources/__Snapshots__/Application/15-5-en-GB-iPhone-13-Pro-Max.home.png new file mode 100644 index 000000000..5123d410a --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/15-5-en-GB-iPhone-13-Pro-Max.home.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6da901a8a5a795ef0e06045cc30dcd153528f8d129cf4f399d8cf79b204cf2c +size 123242 diff --git a/UITests/Sources/__Snapshots__/Application/15-5-fr-FR-iPad-9th-generation.home.png b/UITests/Sources/__Snapshots__/Application/15-5-fr-FR-iPad-9th-generation.home.png new file mode 100644 index 000000000..4009c18f2 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/15-5-fr-FR-iPad-9th-generation.home.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:312c0260f2fb2dfa3c2203878407b5736781d33d406ee2e1844dd17f83ca60c5 +size 114312 diff --git a/UITests/Sources/__Snapshots__/Application/15-5-fr-FR-iPhone-13-Pro-Max.home.png b/UITests/Sources/__Snapshots__/Application/15-5-fr-FR-iPhone-13-Pro-Max.home.png new file mode 100644 index 000000000..e333be94a --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/15-5-fr-FR-iPhone-13-Pro-Max.home.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7572501ece85e6a54ed9a77e1f6b0df187578d6a182b8e3b733e501fe7395eda +size 128434