mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
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:
parent
b9f8fb0b6f
commit
bdc83dac27
@ -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 = "<group>"; };
|
||||
7E532D95330139D118A9BF88 /* BugReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportViewModel.swift; sourceTree = "<group>"; };
|
||||
7FFCC48E7F701B6C24484593 /* WeakDictionaryKeyReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakDictionaryKeyReference.swift; sourceTree = "<group>"; };
|
||||
8023C7413A426FBF0A52B684 /* RoundedCorner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCorner.swift; sourceTree = "<group>"; };
|
||||
8023C7413A426FBF0A52B684 /* RoundedCornerShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCornerShape.swift; sourceTree = "<group>"; };
|
||||
804F9B0FABE093C7284CD09B /* TimelineItemList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemList.swift; sourceTree = "<group>"; };
|
||||
8140010A796DB2C7977B6643 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
8166F121C79C7B62BF01D508 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = pt; path = pt.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
@ -629,7 +631,7 @@
|
||||
8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineProvider.swift; sourceTree = "<group>"; };
|
||||
8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = "<group>"; };
|
||||
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = "<group>"; };
|
||||
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = "<group>"; };
|
||||
8F7D42E66E939B709C1EC390 /* MockRoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomSummaryProvider.swift; sourceTree = "<group>"; };
|
||||
9010EE0CC913D095887EF36E /* OIDCService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCService.swift; sourceTree = "<group>"; };
|
||||
90733775209F4D4D366A268F /* RootRouterType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouterType.swift; sourceTree = "<group>"; };
|
||||
@ -795,6 +797,8 @@
|
||||
E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIFont+AttributedStringBuilder.m"; sourceTree = "<group>"; };
|
||||
E9D059BFE329BE09B6D96A9F /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ro; path = ro.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
EBE5502760CF6CA2D7201883 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
ECA5A34528DC837E0024C8BE /* AvatarSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarSize.swift; sourceTree = "<group>"; };
|
||||
ECA5A34728DC959F0024C8BE /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
||||
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; };
|
||||
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
||||
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = "<group>"; };
|
||||
@ -1009,7 +1013,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0960A7F5C1B0B6679BDF26F9 /* ElementToggleStyle.swift */,
|
||||
8023C7413A426FBF0A52B684 /* RoundedCorner.swift */,
|
||||
8023C7413A426FBF0A52B684 /* RoundedCornerShape.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@ -1097,6 +1101,7 @@
|
||||
A40C19719687984FD9478FBE /* Task.swift */,
|
||||
287FC98AF2664EAD79C0D902 /* UIDevice.swift */,
|
||||
227AC5D71A4CE43512062243 /* URL.swift */,
|
||||
ECA5A34728DC959F0024C8BE /* ImageCache.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@ -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 */,
|
||||
|
@ -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!";
|
||||
|
@ -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))")
|
||||
|
||||
|
@ -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
|
||||
|
83
ElementX/Sources/Other/AvatarSize.swift
Normal file
83
ElementX/Sources/Other/AvatarSize.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
26
ElementX/Sources/Other/Extensions/ImageCache.swift
Normal file
26
ElementX/Sources/Other/Extensions/ImageCache.swift
Normal file
@ -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
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,8 @@ enum HomeScreenViewAction {
|
||||
}
|
||||
|
||||
struct HomeScreenViewState: BindableState {
|
||||
var userID: String
|
||||
var userDisplayName: String?
|
||||
var userAvatar: UIImage?
|
||||
|
||||
var showSessionVerificationBanner = false
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -40,6 +40,7 @@ enum RoomScreenViewAction {
|
||||
case sendMessage
|
||||
case sendReaction(key: String, eventID: String)
|
||||
case cancelReply
|
||||
case viewDisappeared
|
||||
}
|
||||
|
||||
struct RoomScreenViewState: BindableState {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -41,6 +41,9 @@ struct RoomScreen: View {
|
||||
}
|
||||
}
|
||||
.alert(item: $context.alertInfo) { $0.alert }
|
||||
.onDisappear {
|
||||
context.send(viewAction: .viewDisappeared)
|
||||
}
|
||||
}
|
||||
|
||||
private func sendMessage() {
|
||||
|
@ -144,7 +144,7 @@ struct TimelineItemBubbledStylerView<Content: View>: 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)
|
||||
// )
|
||||
}
|
||||
|
@ -58,7 +58,6 @@ struct EmoteRoomTimelineView_Previews: PreviewProvider {
|
||||
EmoteRoomTimelineItem(id: UUID().uuidString,
|
||||
text: text,
|
||||
timestamp: timestamp,
|
||||
shouldShowSenderDetails: true,
|
||||
inGroupState: .single,
|
||||
isOutgoing: false,
|
||||
senderId: senderId)
|
||||
|
@ -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 {
|
||||
|
@ -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",
|
||||
|
@ -59,7 +59,6 @@ struct NoticeRoomTimelineView_Previews: PreviewProvider {
|
||||
NoticeRoomTimelineItem(id: UUID().uuidString,
|
||||
text: text,
|
||||
timestamp: timestamp,
|
||||
shouldShowSenderDetails: true,
|
||||
inGroupState: .single,
|
||||
isOutgoing: false,
|
||||
senderId: senderId)
|
||||
|
@ -25,7 +25,7 @@ struct PlaceholderAvatarImage: View {
|
||||
bgColor
|
||||
Text(textForImage)
|
||||
.padding(4)
|
||||
.foregroundColor(.element.background)
|
||||
.foregroundColor(.white)
|
||||
.font(.title2.bold())
|
||||
}
|
||||
.aspectRatio(1, contentMode: .fill)
|
||||
|
@ -51,7 +51,6 @@ struct RedactedRoomTimelineView_Previews: PreviewProvider {
|
||||
RedactedRoomTimelineItem(id: UUID().uuidString,
|
||||
text: text,
|
||||
timestamp: timestamp,
|
||||
shouldShowSenderDetails: true,
|
||||
inGroupState: .single,
|
||||
isOutgoing: false,
|
||||
senderId: senderId)
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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)")
|
||||
|
@ -25,6 +25,7 @@ enum UITestScreenIdentifier: String {
|
||||
case analyticsPrompt
|
||||
case simpleRegular
|
||||
case simpleUpgrade
|
||||
case home
|
||||
case settings
|
||||
case bugReport
|
||||
case bugReportWithScreenshot
|
||||
|
@ -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"),
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
BIN
UITests/Sources/__Snapshots__/Application/15-5-de-DE-iPad-9th-generation.home.png
(Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/15-5-de-DE-iPad-9th-generation.home.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/15-5-de-DE-iPhone-13-Pro-Max.home.png
(Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/15-5-de-DE-iPhone-13-Pro-Max.home.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/15-5-en-GB-iPad-9th-generation.home.png
(Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/15-5-en-GB-iPad-9th-generation.home.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/15-5-en-GB-iPhone-13-Pro-Max.home.png
(Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/15-5-en-GB-iPhone-13-Pro-Max.home.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/15-5-fr-FR-iPad-9th-generation.home.png
(Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/15-5-fr-FR-iPad-9th-generation.home.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/15-5-fr-FR-iPhone-13-Pro-Max.home.png
(Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/15-5-fr-FR-iPhone-13-Pro-Max.home.png
(Stored with Git LFS)
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user