diff --git a/DesignKit/Sources/TextFields/BorderedInputFieldStyle.swift b/DesignKit/Sources/TextFields/BorderedInputFieldStyle.swift index e6ecda634..a503e3ac0 100644 --- a/DesignKit/Sources/TextFields/BorderedInputFieldStyle.swift +++ b/DesignKit/Sources/TextFields/BorderedInputFieldStyle.swift @@ -85,7 +85,7 @@ public struct BorderedInputFieldStyle: TextFieldStyle { .clipShape(rect) .overlay(rect.stroke(borderColor, lineWidth: borderWidth)) .introspectTextField { textField in - if let returnKey = returnKey { + if let returnKey { textField.returnKeyType = returnKey } diff --git a/DesignKit/Sources/TextFields/ElementTextFieldStyle.swift b/DesignKit/Sources/TextFields/ElementTextFieldStyle.swift index 07aeb1e76..5285589b7 100644 --- a/DesignKit/Sources/TextFields/ElementTextFieldStyle.swift +++ b/DesignKit/Sources/TextFields/ElementTextFieldStyle.swift @@ -53,7 +53,7 @@ public struct ElementTextFieldStyle: TextFieldStyle { public func _body(configuration: TextField<_Label>) -> some View { VStack(alignment: .leading, spacing: 8) { - if let labelText = labelText { + if let labelText { Text(labelText) .font(.element.subheadline) .foregroundColor(labelColor) @@ -63,7 +63,7 @@ public struct ElementTextFieldStyle: TextFieldStyle { .textFieldStyle(BorderedInputFieldStyle(isEditing: isFocused, isError: isError, returnKey: nil)) .focused($isFocused) - if let footerText = footerText { + if let footerText { Text(footerText) .font(.element.footnote) .foregroundColor(footerColor) diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index a6d4cd2d3..218808689 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -44,6 +44,7 @@ 1555A7643D85187D4851040C /* TemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4549FCB53F43DB0B278374BC /* TemplateScreen.swift */; }; 157E5FDDF419C0B2CA7E2C28 /* TimelineItemBubbledStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A2932515EA11D3DD8A3506 /* TimelineItemBubbledStylerView.swift */; }; 15D1F9C415D9C921643BA82E /* UserIndicatorRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B73D5E21F524A9BE44448D /* UserIndicatorRequest.swift */; }; + 15D867E638BFD0E5E71DB1EF /* List.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AFEF3AC64B1358083F76B8B /* List.swift */; }; 165A883C29998EC779465068 /* SoftLogoutViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC38904A9663F7FAFD47457 /* SoftLogoutViewModelProtocol.swift */; }; 1702981A8085BE4FB0EC001B /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = D33116993D54FADC0C721C1F /* Application.swift */; }; 172E6E9A612ADCF10A62CF13 /* BugReportServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A68BCE6438873D2661D93D0 /* BugReportServiceProtocol.swift */; }; @@ -310,7 +311,6 @@ D0619D2E6B9C511190FBEB95 /* RoomMessageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607974D08BD2AF83725D817A /* RoomMessageProtocol.swift */; }; D5EA4C6C80579279770D5804 /* ImageRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */; }; D6417E5A799C3C7F14F9EC0A /* SessionVerificationViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3069ADED46D063202FE7698 /* SessionVerificationViewModelProtocol.swift */; }; - D826154612415D2A3BB6EBF3 /* ListTableViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E854E7CF531DAC5CBEBDC75 /* ListTableViewAdapter.swift */; }; D8359F67AF3A83516E9083C1 /* MockUserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4756C5A8C8649AD6C10C615 /* MockUserSession.swift */; }; D85D4FA590305180B4A41795 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3073CCD77D906B330BC1D6 /* Tests.swift */; }; D876EC0FED3B6D46C806912A /* AvatarSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24B88AD3D1599E8CB1376E0 /* AvatarSize.swift */; }; @@ -331,6 +331,7 @@ E481C8FDCB6C089963C95344 /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 527578916BD388A09F5A8036 /* DTCoreText */; }; E5895C74615CBE8462FB840F /* SessionVerificationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCF86010A0A719A9A50EEC59 /* SessionVerificationCoordinator.swift */; }; E81EEC1675F2371D12A880A3 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61ADFB893DEF81E58DF3FAB9 /* MockRoomTimelineController.swift */; }; + E8AFB40CC7C3DF1930DA89E2 /* ListCollectionViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C923DD8D9946257D46806 /* ListCollectionViewAdapter.swift */; }; E96005321849DBD7C72A28F2 /* UITestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */; }; EA1E7949533E19C6D862680A /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885D8C42DD17625B5261BEFF /* MediaProvider.swift */; }; EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */; }; @@ -460,6 +461,7 @@ 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilder.swift; sourceTree = ""; }; 2AE83A3DD63BCFBB956FE5CB /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nl; path = nl.lproj/Localizable.stringsdict; sourceTree = ""; }; 2AEA20A6B4883E60469ACF8F /* SoftLogoutCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutCoordinator.swift; sourceTree = ""; }; + 2AFEF3AC64B1358083F76B8B /* List.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = List.swift; sourceTree = ""; }; 2B80895CE021B49847BD7D74 /* TemplateViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateViewModelProtocol.swift; sourceTree = ""; }; 2B9BCACD0CC4CB8E37F17732 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = lt; path = lt.lproj/Localizable.stringsdict; sourceTree = ""; }; 2CA028DCD4157F9A1F999827 /* BackgroundTaskProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTaskProtocol.swift; sourceTree = ""; }; @@ -484,7 +486,6 @@ 398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadableFrameModifier.swift; sourceTree = ""; }; 399427358A80BA2848E698A2 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/Localizable.strings"; sourceTree = ""; }; 39EBB6903EFD4236B8D11A42 /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "fr-CA"; path = "fr-CA.lproj/Localizable.stringsdict"; sourceTree = ""; }; - 3A008A7A3B4DF58C1A7AD142 /* changelog.d */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.dtrace; path = changelog.d; sourceTree = ""; }; 3A4427F9E0571B4E6E048A2B /* KeychainController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainController.swift; sourceTree = ""; }; 3ACBDC1D28EFB7789EB467E0 /* MockRoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomProxy.swift; sourceTree = ""; }; 3B5B535DA49C54523FF7A412 /* nn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nn; path = nn.lproj/Localizable.strings; sourceTree = ""; }; @@ -532,7 +533,6 @@ 4D6E4C37E9F0E53D3DF951AC /* HomeScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenUITests.swift; sourceTree = ""; }; 4DF56C3239EA3C16951E1E66 /* is */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = is; path = is.lproj/Localizable.strings; sourceTree = ""; }; 4E2245243369B99216C7D84E /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; - 4E854E7CF531DAC5CBEBDC75 /* ListTableViewAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTableViewAdapter.swift; sourceTree = ""; }; 4F0CB536D1C3CC15AA740CC6 /* AuthenticationServiceProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxyProtocol.swift; sourceTree = ""; }; 4F49CDE349C490D617332770 /* NoticeRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineItem.swift; sourceTree = ""; }; 4F5F0662483ED69791D63B16 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = et; path = et.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -809,6 +809,7 @@ 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 = ""; }; ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = ""; }; + ED0C923DD8D9946257D46806 /* ListCollectionViewAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCollectionViewAdapter.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 = ""; }; EDB6E40BAD4504D899FAAC9A /* TemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateViewModel.swift; sourceTree = ""; }; @@ -1097,7 +1098,6 @@ 405B00F139AEE3994601B36A = { isa = PBXGroup; children = ( - 3A008A7A3B4DF58C1A7AD142 /* changelog.d */, 5D26A086A8278D39B5756D6F /* project.yml */, 99B9B46F2D621380428E68F7 /* ElementX */, A4852B57D55D71EEBFCD931D /* UnitTests */, @@ -1130,6 +1130,7 @@ A9FAFE1C2149E6AC8156ED2B /* Collection.swift */, E26747B3154A5DBC3A7E24A5 /* Image.swift */, 4E2245243369B99216C7D84E /* ImageCache.swift */, + 2AFEF3AC64B1358083F76B8B /* List.swift */, 40B21E611DADDEF00307E7AC /* String.swift */, A40C19719687984FD9478FBE /* Task.swift */, 287FC98AF2664EAD79C0D902 /* UIDevice.swift */, @@ -1399,7 +1400,7 @@ 79023E5904B155E8E2B8B502 /* View */ = { isa = PBXGroup; children = ( - 4E854E7CF531DAC5CBEBDC75 /* ListTableViewAdapter.swift */, + ED0C923DD8D9946257D46806 /* ListCollectionViewAdapter.swift */, E18CF12478983A5EB390FB26 /* MessageComposer.swift */, BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */, 422724361B6555364C43281E /* RoomHeaderView.swift */, @@ -2259,6 +2260,7 @@ }; 98CA896D84BFD53B2554E891 /* ⚠️ SwiftLint */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -2277,6 +2279,7 @@ }; A7130911BCB2DF3D249A1836 /* 🛠 SwiftGen */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -2295,6 +2298,7 @@ }; B35AB66424BB30087EEE408C /* 🧹 SwiftFormat */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -2428,7 +2432,8 @@ F4C3FEDB1B3A05376A1723A3 /* KeychainController.swift in Sources */, F2DD8661B5C0BA2BB526FA6C /* KeychainControllerProtocol.swift in Sources */, 9C9E48A627C7C166084E3F5B /* LabelledActivityIndicatorView.swift in Sources */, - D826154612415D2A3BB6EBF3 /* ListTableViewAdapter.swift in Sources */, + 15D867E638BFD0E5E71DB1EF /* List.swift in Sources */, + E8AFB40CC7C3DF1930DA89E2 /* ListCollectionViewAdapter.swift in Sources */, 83E5054739949181CA981193 /* LoginCoordinator.swift in Sources */, 872A6457DF573AF8CEAE927A /* LoginHomeserver.swift in Sources */, CEB8FB1269DE20536608B957 /* LoginMode.swift in Sources */, @@ -2949,8 +2954,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MACOSX_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MACOSX_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3014,8 +3019,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MACOSX_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MACOSX_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index bb7b139ac..7d54262a3 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -21,7 +21,7 @@ import UIKit struct ServiceLocator { fileprivate static var serviceLocator: ServiceLocator? static var shared: ServiceLocator { - guard let serviceLocator = serviceLocator else { + guard let serviceLocator else { fatalError("The service locator should be setup at this point") } @@ -46,7 +46,7 @@ class AppCoordinator: Coordinator { private var userSession: UserSessionProtocol! { didSet { deobserveUserSessionChanges() - if let userSession = userSession, !userSession.isSoftLogout { + if let userSession, !userSession.isSoftLogout { observeUserSessionChanges() } } @@ -135,7 +135,7 @@ class AppCoordinator: Coordinator { // swiftlint:disable:next cyclomatic_complexity private func setupStateMachine() { stateMachine.addTransitionHandler { [weak self] context in - guard let self = self else { return } + guard let self else { return } switch (context.fromState, context.event, context.toState) { case (.initial, .startWithAuthentication, .signedOut): @@ -297,7 +297,7 @@ class AppCoordinator: Coordinator { userSession.callbacks .receive(on: DispatchQueue.main) .sink { [weak self] callback in - guard let self = self else { return } + guard let self else { return } switch callback { case .didReceiveAuthError(let isSoftLogout): self.stateMachine.processEvent(.remoteSignOut(isSoft: isSoftLogout)) diff --git a/ElementX/Sources/Other/Benchmark.swift b/ElementX/Sources/Other/Benchmark.swift index 0394754f6..cbcdeac43 100644 --- a/ElementX/Sources/Other/Benchmark.swift +++ b/ElementX/Sources/Other/Benchmark.swift @@ -29,7 +29,7 @@ struct Benchmark { let startTime = CFAbsoluteTimeGetCurrent() trackingIdentifiers[identifier] = startTime - if let message = message { + if let message { MXLog.verbose("⏰ \(message).") } } @@ -45,7 +45,7 @@ struct Benchmark { } let elapsedTime = CFAbsoluteTimeGetCurrent() - start - if let message = message { + if let message { MXLog.verbose("⏰ \(message). Elapsed time: \(elapsedTime.round(to: 4)) seconds.") } else { MXLog.verbose("⏰ Elapsed time: \(elapsedTime.round(to: 4)) seconds.") diff --git a/ElementX/Sources/Other/Extensions/List.swift b/ElementX/Sources/Other/Extensions/List.swift new file mode 100644 index 000000000..a6c8ecacd --- /dev/null +++ b/ElementX/Sources/Other/Extensions/List.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 Introspect +import SwiftUI + +extension View { + /// Finds a `UICollectionView` from a `SwiftUI.List`, or `SwiftUI.List` child. + /// Stop gap until https://github.com/siteline/SwiftUI-Introspect/pull/169 + func introspectCollectionView(customize: @escaping (UICollectionView) -> Void) -> some View { + introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize) + } +} diff --git a/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift b/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift index b56c2856a..3be6ef8a9 100644 --- a/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift +++ b/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift @@ -29,7 +29,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol { } func fromPlain(_ string: String?) -> AttributedString? { - guard let string = string else { + guard let string else { return nil } @@ -54,7 +54,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol { // that could happen with the default HTML renderer of NSAttributedString which is a // webview. func fromHTML(_ htmlString: String?) -> AttributedString? { - guard let htmlString = htmlString, + guard let htmlString, let data = htmlString.data(using: .utf8) else { return nil } @@ -93,7 +93,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol { } func blockquoteCoalescedComponentsFrom(_ attributedString: AttributedString?) -> [AttributedStringBuilderComponent]? { - guard let attributedString = attributedString else { + guard let attributedString else { return nil } diff --git a/ElementX/Sources/Other/HTMLParsing/DTHTMLElement+AttributedStringBuilder.swift b/ElementX/Sources/Other/HTMLParsing/DTHTMLElement+AttributedStringBuilder.swift index 1a11a9407..7b7d7da71 100644 --- a/ElementX/Sources/Other/HTMLParsing/DTHTMLElement+AttributedStringBuilder.swift +++ b/ElementX/Sources/Other/HTMLParsing/DTHTMLElement+AttributedStringBuilder.swift @@ -22,7 +22,7 @@ public extension DTHTMLElement { /// - Parameters: /// - font: The default font to use when resetting the content of any unsupported tags. @objc func sanitize(font: UIFont) { - if let name = name, !Self.allowedHTMLTags.contains(name) { + if let name, !Self.allowedHTMLTags.contains(name) { // This is an unsupported tag. // Remove any attachments to fix rendering. textAttachment = nil diff --git a/ElementX/Sources/Other/Routers/NavigationRouter.swift b/ElementX/Sources/Other/Routers/NavigationRouter.swift index d55899c5c..46a6b61b5 100755 --- a/ElementX/Sources/Other/Routers/NavigationRouter.swift +++ b/ElementX/Sources/Other/Routers/NavigationRouter.swift @@ -95,7 +95,7 @@ final class NavigationRouter: NSObject, NavigationRouterType { self.willPopViewController($0) } - if let popCompletion = popCompletion { + if let popCompletion { completions[controller] = popCompletion } diff --git a/ElementX/Sources/Other/SwiftUI/ErrorHandling/AlertInfo.swift b/ElementX/Sources/Other/SwiftUI/ErrorHandling/AlertInfo.swift index 547a2bd2c..ea6545310 100644 --- a/ElementX/Sources/Other/SwiftUI/ErrorHandling/AlertInfo.swift +++ b/ElementX/Sources/Other/SwiftUI/ErrorHandling/AlertInfo.swift @@ -60,14 +60,14 @@ extension AlertInfo { extension AlertInfo { private var messageText: Text? { - guard let message = message else { return nil } + guard let message else { return nil } return Text(message) } /// Returns a SwiftUI `Alert` created from this alert info, using default button /// styles for both primary and (if set) secondary buttons. var alert: Alert { - if let secondaryButton = secondaryButton { + if let secondaryButton { return Alert(title: Text(title), message: messageText, primaryButton: alertButton(for: primaryButton), diff --git a/ElementX/Sources/Other/SwiftUI/ViewModel/StateStoreViewModel.swift b/ElementX/Sources/Other/SwiftUI/ViewModel/StateStoreViewModel.swift index 6210b7576..f2bf1f332 100644 --- a/ElementX/Sources/Other/SwiftUI/ViewModel/StateStoreViewModel.swift +++ b/ElementX/Sources/Other/SwiftUI/ViewModel/StateStoreViewModel.swift @@ -97,7 +97,7 @@ class StateStoreViewModel { init(initialViewState: State) { context = Context(initialViewState: initialViewState) context.viewActions.sink { [weak self] action in - guard let self = self else { return } + guard let self else { return } Task { await self.process(viewAction: action) } } diff --git a/ElementX/Sources/Other/UserIndicators/ActivityIndicatorPresenter.swift b/ElementX/Sources/Other/UserIndicators/ActivityIndicatorPresenter.swift index da33a5f7b..a3968c530 100755 --- a/ElementX/Sources/Other/UserIndicators/ActivityIndicatorPresenter.swift +++ b/ElementX/Sources/Other/UserIndicators/ActivityIndicatorPresenter.swift @@ -41,7 +41,7 @@ final class ActivityIndicatorPresenter: ActivityIndicatorPresenterType { func presentActivityIndicator(on view: UIView, animated: Bool, completion: (() -> Void)? = nil) { if presentingView != nil { - if let completion = completion { + if let completion { completion() } return @@ -88,7 +88,7 @@ final class ActivityIndicatorPresenter: ActivityIndicatorPresenterType { } func removeCurrentActivityIndicator(animated: Bool, completion: (() -> Void)? = nil) { - guard let presentingView = presentingView, + guard let presentingView, let backgroundOverlayView = backgroundOverlayView, let activityIndicatorView = activityIndicatorView else { return diff --git a/ElementX/Sources/Other/UserIndicators/FullscreenLoadingViewPresenter.swift b/ElementX/Sources/Other/UserIndicators/FullscreenLoadingViewPresenter.swift index 70c819e38..b633f4d17 100644 --- a/ElementX/Sources/Other/UserIndicators/FullscreenLoadingViewPresenter.swift +++ b/ElementX/Sources/Other/UserIndicators/FullscreenLoadingViewPresenter.swift @@ -35,7 +35,7 @@ class FullscreenLoadingViewPresenter: UserIndicatorViewPresentable { while presentingController?.navigationController != nil { presentingController = presentingController?.navigationController } - guard let presentingController = presentingController else { + guard let presentingController else { return } @@ -59,7 +59,7 @@ class FullscreenLoadingViewPresenter: UserIndicatorViewPresentable { } func dismiss() { - guard let view = view, view.superview != nil else { + guard let view, view.superview != nil else { return } diff --git a/ElementX/Sources/Other/UserIndicators/RectangleToastView.swift b/ElementX/Sources/Other/UserIndicators/RectangleToastView.swift index a59d0d78d..e7192e1d6 100644 --- a/ElementX/Sources/Other/UserIndicators/RectangleToastView.swift +++ b/ElementX/Sources/Other/UserIndicators/RectangleToastView.swift @@ -63,7 +63,7 @@ class RectangleToastView: UIView { image: UIImage? = nil) { super.init(frame: .zero) - if let image = image { + if let image { imageView.image = image NSLayoutConstraint.activate([ imageView.widthAnchor.constraint(equalToConstant: image.size.width), diff --git a/ElementX/Sources/Other/UserIndicators/ToastViewPresenter.swift b/ElementX/Sources/Other/UserIndicators/ToastViewPresenter.swift index c745f605e..adc8a348e 100644 --- a/ElementX/Sources/Other/UserIndicators/ToastViewPresenter.swift +++ b/ElementX/Sources/Other/UserIndicators/ToastViewPresenter.swift @@ -66,7 +66,7 @@ class ToastViewPresenter: UserIndicatorViewPresentable { } func dismiss() { - guard let view = view, view.superview != nil else { + guard let view, view.superview != nil else { return } diff --git a/ElementX/Sources/Screens/AnalyticsPrompt/AnalyticsPromptCoordinator.swift b/ElementX/Sources/Screens/AnalyticsPrompt/AnalyticsPromptCoordinator.swift index cd64695c7..716e4e4c5 100644 --- a/ElementX/Sources/Screens/AnalyticsPrompt/AnalyticsPromptCoordinator.swift +++ b/ElementX/Sources/Screens/AnalyticsPrompt/AnalyticsPromptCoordinator.swift @@ -56,7 +56,7 @@ final class AnalyticsPromptCoordinator: Coordinator, Presentable { analyticsPromptViewModel.callback = { [weak self] result in MXLog.debug("AnalyticsPromptViewModel did complete with result: \(result).") - guard let self = self else { return } + guard let self else { return } switch result { case .enable: diff --git a/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift b/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift index a1c996007..21279004d 100644 --- a/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift @@ -59,7 +59,7 @@ class AuthenticationCoordinator: Coordinator, Presentable { let coordinator = SplashScreenCoordinator() coordinator.callback = { [weak self] action in - guard let self = self else { return } + guard let self else { return } switch action { case .login: Task { await self.startAuthentication() } @@ -93,7 +93,7 @@ class AuthenticationCoordinator: Coordinator, Presentable { let coordinator = ServerSelectionCoordinator(parameters: parameters) coordinator.callback = { [weak self] action in - guard let self = self else { return } + guard let self else { return } switch action { case .updated: @@ -117,7 +117,7 @@ class AuthenticationCoordinator: Coordinator, Presentable { let coordinator = LoginCoordinator(parameters: parameters) coordinator.callback = { [weak self] action in - guard let self = self else { return } + guard let self else { return } switch action { case .signedIn(let userSession): @@ -138,7 +138,7 @@ class AuthenticationCoordinator: Coordinator, Presentable { let coordinator = AnalyticsPromptCoordinator(parameters: parameters) coordinator.callback = { [weak self] in - guard let self = self else { return } + guard let self else { return } self.delegate?.authenticationCoordinator(self, didLoginWithSession: userSession) } diff --git a/ElementX/Sources/Screens/Authentication/LoginScreen/LoginCoordinator.swift b/ElementX/Sources/Screens/Authentication/LoginScreen/LoginCoordinator.swift index 4e322b346..f93a181ef 100644 --- a/ElementX/Sources/Screens/Authentication/LoginScreen/LoginCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/LoginScreen/LoginCoordinator.swift @@ -78,7 +78,7 @@ final class LoginCoordinator: Coordinator, Presentable { MXLog.debug("Did start.") loginViewModel.callback = { [weak self] action in - guard let self = self else { return } + guard let self else { return } MXLog.debug("LoginViewModel did callback with result: \(action).") switch action { @@ -144,7 +144,7 @@ final class LoginCoordinator: Coordinator, Presentable { } private func loginWithOIDC() { - guard let oidcUserAgent = oidcUserAgent else { + guard let oidcUserAgent else { handleError(AuthenticationServiceError.oidcError(.notSupported)) return } @@ -215,7 +215,7 @@ final class LoginCoordinator: Coordinator, Presentable { hasModalPresentation: true) let coordinator = ServerSelectionCoordinator(parameters: parameters) coordinator.callback = { [weak self, weak coordinator] action in - guard let self = self, let coordinator = coordinator else { return } + guard let self, let coordinator = coordinator else { return } self.serverSelectionCoordinator(coordinator, didCompleteWith: action) } diff --git a/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionCoordinator.swift b/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionCoordinator.swift index 087e2e1c9..aeac36595 100644 --- a/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionCoordinator.swift @@ -67,7 +67,7 @@ final class ServerSelectionCoordinator: Coordinator, Presentable { MXLog.debug("Did start.") serverSelectionViewModel.callback = { [weak self] action in - guard let self = self else { return } + guard let self else { return } MXLog.debug("ServerSelectionViewModel did callback with action: \(action).") switch action { diff --git a/ElementX/Sources/Screens/Authentication/SoftLogout/SoftLogoutCoordinator.swift b/ElementX/Sources/Screens/Authentication/SoftLogout/SoftLogoutCoordinator.swift index 21e8a3587..05fa745dd 100644 --- a/ElementX/Sources/Screens/Authentication/SoftLogout/SoftLogoutCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/SoftLogout/SoftLogoutCoordinator.swift @@ -89,7 +89,7 @@ final class SoftLogoutCoordinator: Coordinator, Presentable { MXLog.debug("[SoftLogoutCoordinator] did start.") softLogoutViewModel.callback = { [weak self] result in - guard let self = self else { return } + guard let self else { return } MXLog.debug("[SoftLogoutCoordinator] SoftLogoutViewModel did complete with result: \(result).") switch result { @@ -154,7 +154,7 @@ final class SoftLogoutCoordinator: Coordinator, Presentable { } private func loginWithOIDC() { - guard let oidcUserAgent = oidcUserAgent else { + guard let oidcUserAgent else { handleError(AuthenticationServiceError.oidcError(.notSupported)) return } diff --git a/ElementX/Sources/Screens/BugReport/BugReportCoordinator.swift b/ElementX/Sources/Screens/BugReport/BugReportCoordinator.swift index e8484501f..2ce56a880 100644 --- a/ElementX/Sources/Screens/BugReport/BugReportCoordinator.swift +++ b/ElementX/Sources/Screens/BugReport/BugReportCoordinator.swift @@ -59,7 +59,7 @@ final class BugReportCoordinator: Coordinator, Presentable { func start() { MXLog.debug("Did start.") bugReportViewModel.callback = { [weak self] result in - guard let self = self else { return } + guard let self else { return } MXLog.debug("BugReportViewModel did complete with result: \(result).") switch result { case .submitStarted: diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift index e0dd6a0c7..53719434b 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift @@ -59,7 +59,7 @@ final class HomeScreenCoordinator: Coordinator, Presentable { hostingController = UIHostingController(rootView: view) viewModel.callback = { [weak self] action in - guard let self = self else { return } + guard let self else { return } switch action { case .selectRoom(let roomIdentifier): diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index fbf1613b3..4942561d2 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -46,7 +46,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol timelineController.callbacks .receive(on: DispatchQueue.main) .sink { [weak self] callback in - guard let self = self else { return } + guard let self else { return } switch callback { case .updatedTimelineItems: @@ -66,7 +66,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol buildTimelineViews() - if let roomAvatarUrl = roomAvatarUrl { + if let roomAvatarUrl { Task { if case let .success(avatar) = await mediaProvider.loadImageFromURLString(roomAvatarUrl, avatarSize: .room(on: .timeline)) { diff --git a/ElementX/Sources/Screens/RoomScreen/View/ListTableViewAdapter.swift b/ElementX/Sources/Screens/RoomScreen/View/ListCollectionViewAdapter.swift similarity index 66% rename from ElementX/Sources/Screens/RoomScreen/View/ListTableViewAdapter.swift rename to ElementX/Sources/Screens/RoomScreen/View/ListCollectionViewAdapter.swift index f67e188d0..2cc7baf1d 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/ListTableViewAdapter.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/ListCollectionViewAdapter.swift @@ -17,7 +17,7 @@ import Combine import UIKit -class ListTableViewAdapter: NSObject, UITableViewDelegate { +class ListCollectionViewAdapter: NSObject, UICollectionViewDelegate { private enum ContentOffsetDetails { case topOffset(previousVisibleIndexPath: IndexPath, previousItemCount: Int) case bottomOffset @@ -34,7 +34,7 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate { private var isAnimatingKeyboardAppearance = false private var previousFrame: CGRect = .zero - private(set) var tableView: UITableView? + private(set) var collectionView: UICollectionView? let scrollViewDidRestPublisher = PassthroughSubject() let scrollViewTopVisiblePublisher = CurrentValueSubject(false) @@ -45,38 +45,36 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate { bottomDetectionOffset = 0.0 } - init(tableView: UITableView, topDetectionOffset: CGFloat, bottomDetectionOffset: CGFloat) { - self.tableView = tableView + init(collectionView: UICollectionView, topDetectionOffset: CGFloat, bottomDetectionOffset: CGFloat) { + self.collectionView = collectionView self.topDetectionOffset = topDetectionOffset self.bottomDetectionOffset = bottomDetectionOffset super.init() - tableView.clipsToBounds = true - tableView.keyboardDismissMode = .onDrag + collectionView.clipsToBounds = true + collectionView.keyboardDismissMode = .onDrag - registerContentOfffsetObserver() + registerContentOffsetObserver() registerBoundsObserver() NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow(notification:)), name: UIResponder.keyboardDidShowNotification, object: nil) - tableView.panGestureRecognizer.addTarget(self, action: #selector(handlePanGesture(_:))) + collectionView.panGestureRecognizer.addTarget(self, action: #selector(handlePanGesture(_:))) } func saveCurrentOffset() { - guard let tableView = tableView, - tableView.numberOfSections > 0 else { + guard let collectionView, + collectionView.numberOfSections > 0 else { return } if computeIsBottomVisible() { offsetDetails = .bottomOffset - } else if computeIsTopVisible() { - if let topIndexPath = tableView.indexPathsForVisibleRows?.first { - offsetDetails = .topOffset(previousVisibleIndexPath: topIndexPath, - previousItemCount: tableView.numberOfRows(inSection: 0)) - } + } else if computeIsTopVisible(), let topIndexPath = collectionView.indexPathsForVisibleItems.first { + offsetDetails = .topOffset(previousVisibleIndexPath: topIndexPath, + previousItemCount: collectionView.numberOfItems(inSection: 0)) } } @@ -85,20 +83,20 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate { offsetDetails = nil } - guard let tableView = tableView, - tableView.numberOfSections > 0 else { + guard let collectionView, + collectionView.numberOfSections > 0 else { return } - let currentItemCount = tableView.numberOfRows(inSection: 0) + let currentItemCount = collectionView.numberOfItems(inSection: 0) switch offsetDetails { case .bottomOffset: - tableView.scrollToRow(at: .init(row: max(0, currentItemCount - 1), section: 0), at: .bottom, animated: false) + collectionView.scrollToItem(at: .init(item: max(0, currentItemCount - 1), section: 0), at: .bottom, animated: false) case .topOffset(let indexPath, let previousItemCount): - let row = indexPath.row + max(0, currentItemCount - previousItemCount) - if row < currentItemCount { - tableView.scrollToRow(at: .init(row: row, section: 0), at: .top, animated: false) + let item = indexPath.item + max(0, currentItemCount - previousItemCount) + if item < currentItemCount { + collectionView.scrollToItem(at: .init(item: item, section: 0), at: .top, animated: false) } case .none: break @@ -106,35 +104,35 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate { } var isTracking: Bool { - tableView?.isTracking == true + collectionView?.isTracking == true } var isDecelerating: Bool { - tableView?.isDecelerating == true + collectionView?.isDecelerating == true } func scrollToBottom(animated: Bool = false) { - guard let tableView = tableView, - tableView.numberOfSections > 0 else { + guard let collectionView, + collectionView.numberOfSections > 0 else { return } - let currentItemCount = tableView.numberOfRows(inSection: 0) + let currentItemCount = collectionView.numberOfItems(inSection: 0) guard currentItemCount > 1 else { return } - tableView.scrollToRow(at: .init(row: currentItemCount - 1, section: 0), at: .bottom, animated: animated) + collectionView.scrollToItem(at: .init(item: currentItemCount - 1, section: 0), at: .bottom, animated: animated) } // MARK: - Private - private func registerContentOfffsetObserver() { - // Don't attempt stealing the UITableView delegate away from the List. + private func registerContentOffsetObserver() { + // Don't attempt stealing the UICollectionView delegate away from the List. // Doing so results in undefined behavior e.g. context menus not working - contentOffsetObserverToken = tableView?.observe(\.contentOffset, options: .new, changeHandler: { [weak self] _, _ in + contentOffsetObserverToken = collectionView?.observe(\.contentOffset, options: .new) { [weak self] _, _ in self?.handleScrollViewScroll() - }) + } } private func deregisterContentOffsetObserver() { @@ -142,10 +140,10 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate { } private func registerBoundsObserver() { - boundsObserverToken = tableView?.observe(\.frame, options: .new, changeHandler: { [weak self] tableView, _ in - self?.previousFrame = tableView.frame + boundsObserverToken = collectionView?.observe(\.frame, options: .new) { [weak self] collectionView, _ in + self?.previousFrame = collectionView.frame self?.handleScrollViewScroll() - }) + } } private func deregisterBoundsObserver() { @@ -161,18 +159,18 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate { } private func handleScrollViewScroll() { - guard let tableView = tableView else { + guard let collectionView else { return } - let hasScrolledBecauseOfFrameChange = (previousFrame != tableView.frame) + let hasScrolledBecauseOfFrameChange = (previousFrame != collectionView.frame) let shouldPinToBottom = scrollViewBottomVisiblePublisher.value && (isAnimatingKeyboardAppearance || hasScrolledBecauseOfFrameChange) if shouldPinToBottom { deregisterContentOffsetObserver() scrollToBottom() DispatchQueue.main.async { - self.registerContentOfffsetObserver() + self.registerContentOffsetObserver() } return } @@ -187,19 +185,19 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate { scrollViewBottomVisiblePublisher.send(isBottomVisible) } - if !draggingInitiated, tableView.isDragging { + if !draggingInitiated, collectionView.isDragging { draggingInitiated = true - } else if draggingInitiated, !tableView.isDragging { + } else if draggingInitiated, !collectionView.isDragging { draggingInitiated = false scrollViewDidRestPublisher.send(()) } } @objc private func handlePanGesture(_ sender: UIPanGestureRecognizer) { - guard let tableView = tableView, + guard let collectionView, sender.state == .ended, - draggingInitiated == true, - !tableView.isDecelerating else { + draggingInitiated, + !collectionView.isDecelerating else { return } @@ -208,7 +206,7 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate { } private func computeIsTopVisible() -> Bool { - guard let scrollView = tableView else { + guard let scrollView = collectionView else { return false } @@ -216,7 +214,7 @@ class ListTableViewAdapter: NSObject, UITableViewDelegate { } private func computeIsBottomVisible() -> Bool { - guard let scrollView = tableView else { + guard let scrollView = collectionView else { return false } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/PlaceholderAvatarImage.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/PlaceholderAvatarImage.swift index 62bd350e4..6b2106a25 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/PlaceholderAvatarImage.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/PlaceholderAvatarImage.swift @@ -38,7 +38,7 @@ struct PlaceholderAvatarImage: View { } private var bgColor: Color { - guard let contentId = contentId else { + guard let contentId else { return .element.accent } diff --git a/ElementX/Sources/Screens/RoomScreen/View/TimelineItemList.swift b/ElementX/Sources/Screens/RoomScreen/View/TimelineItemList.swift index 76d2d40a7..fb6fca241 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/TimelineItemList.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/TimelineItemList.swift @@ -19,7 +19,7 @@ import Introspect import SwiftUI struct TimelineItemList: View { - @State private var tableViewObserver = ListTableViewAdapter() + @State private var collectionViewObserver = ListCollectionViewAdapter() @State private var timelineItems: [RoomTimelineViewProvider] = [] @State private var hasPendingChanges = false @ObservedObject private var settings = ElementSettings.shared @@ -32,105 +32,106 @@ struct TimelineItemList: View { @State private var viewFrame: CGRect = .zero var body: some View { - // The observer behaves differently when not in an reader - ScrollViewReader { _ in - List { - ProgressView() - .frame(maxWidth: .infinity) - .opacity(context.viewState.isBackPaginating ? 1.0 : 0.0) - .animation(.elementDefault, value: context.viewState.isBackPaginating) + List { + ProgressView() + .frame(maxWidth: .infinity) + .opacity(context.viewState.isBackPaginating ? 1.0 : 0.0) + .animation(.elementDefault, value: context.viewState.isBackPaginating) + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + + // timelineItems won't be set for static Xcode Previews until they're run. + ForEach(isPreview ? context.viewState.items : timelineItems) { timelineItem in + timelineItem + .contextMenu { + context.viewState.contextMenuBuilder?(timelineItem.id) + } + .opacity(opacityForItem(timelineItem)) .listRowBackground(Color.clear) .listRowSeparator(.hidden) - - // No idea why previews don't work otherwise - ForEach(isPreview ? context.viewState.items : timelineItems) { timelineItem in - timelineItem - .contextMenu { - context.viewState.contextMenuBuilder?(timelineItem.id) - } - .opacity(opacityForItem(timelineItem)) - .listRowBackground(Color.clear) - .listRowSeparator(.hidden) - .listRowInsets(settings.timelineStyle.listRowInsets) - .onAppear { - context.send(viewAction: .itemAppeared(id: timelineItem.id)) - } - .onDisappear { - context.send(viewAction: .itemDisappeared(id: timelineItem.id)) - } - .environment(\.openURL, OpenURLAction { url in - context.send(viewAction: .linkClicked(url: url)) - return .systemAction - }) - } + .listRowInsets(settings.timelineStyle.listRowInsets) + .onAppear { + context.send(viewAction: .itemAppeared(id: timelineItem.id)) + } + .onDisappear { + context.send(viewAction: .itemDisappeared(id: timelineItem.id)) + } + .environment(\.openURL, OpenURLAction { url in + context.send(viewAction: .linkClicked(url: url)) + return .systemAction + }) } - .listStyle(.plain) - .background(ViewFrameReader(frame: $viewFrame)) - .environment(\.timelineWidth, viewFrame.width) - .timelineStyle(settings.timelineStyle) - .environment(\.defaultMinListRowHeight, 0.0) - .introspectTableView { tableView in - if tableView == tableViewObserver.tableView { - return - } - - tableViewObserver = ListTableViewAdapter(tableView: tableView, - topDetectionOffset: tableView.bounds.size.height / 3.0, - bottomDetectionOffset: 10.0) - - tableViewObserver.scrollToBottom() - - // Check if there are enough items. Otherwise ask for more - attemptBackPagination() - } - .onAppear { - if timelineItems != context.viewState.items { - timelineItems = context.viewState.items - } - } - .onReceive(scrollToBottomPublisher) { - tableViewObserver.scrollToBottom(animated: true) - } - .onReceive(tableViewObserver.scrollViewTopVisiblePublisher) { isTopVisible in - if !isTopVisible || context.viewState.isBackPaginating { - return - } - - attemptBackPagination() - } - .onReceive(tableViewObserver.scrollViewBottomVisiblePublisher) { isBottomVisible in - bottomVisiblePublisher.send(isBottomVisible) - } - .onChange(of: context.viewState.items) { _ in - // Don't update the list while moving - if tableViewObserver.isDecelerating || tableViewObserver.isTracking { - hasPendingChanges = true - return - } - - tableViewObserver.saveCurrentOffset() + } + .listStyle(.plain) + .background(ViewFrameReader(frame: $viewFrame)) + .environment(\.timelineWidth, viewFrame.width) + .timelineStyle(settings.timelineStyle) + .environment(\.defaultMinListRowHeight, 0.0) + .introspectCollectionView { collectionView in + if collectionView == collectionViewObserver.collectionView { return } + + collectionViewObserver = ListCollectionViewAdapter(collectionView: collectionView, + topDetectionOffset: collectionView.bounds.size.height / 3.0, + bottomDetectionOffset: 10.0) + + collectionViewObserver.scrollToBottom() + + // Check if there are enough items. Otherwise ask for more + attemptBackPagination() + } + .onAppear { + if timelineItems != context.viewState.items { timelineItems = context.viewState.items } - .onReceive(tableViewObserver.scrollViewDidRestPublisher) { - if hasPendingChanges == false { - return - } - - tableViewObserver.saveCurrentOffset() + } + .onReceive(scrollToBottomPublisher) { + collectionViewObserver.scrollToBottom(animated: true) + } + .onReceive(collectionViewObserver.scrollViewTopVisiblePublisher) { isTopVisible in + if !isTopVisible || context.viewState.isBackPaginating { + return + } + + attemptBackPagination() + } + .onReceive(collectionViewObserver.scrollViewBottomVisiblePublisher) { isBottomVisible in + bottomVisiblePublisher.send(isBottomVisible) + } + .onChange(of: context.viewState.items) { _ in + // If the count hasn't changed then don't observe a pagination + guard context.viewState.items.count != timelineItems.count else { timelineItems = context.viewState.items - hasPendingChanges = false + return } - .onChange(of: timelineItems) { _ in - tableViewObserver.restoreSavedOffset() - - // Check if there are enough items. Otherwise ask for more - attemptBackPagination() + + // Don't update the list while moving + if collectionViewObserver.isDecelerating || collectionViewObserver.isTracking { + hasPendingChanges = true + return } + + collectionViewObserver.saveCurrentOffset() + timelineItems = context.viewState.items + } + .onReceive(collectionViewObserver.scrollViewDidRestPublisher) { + if hasPendingChanges == false { + return + } + + collectionViewObserver.saveCurrentOffset() + timelineItems = context.viewState.items + hasPendingChanges = false + } + .onChange(of: timelineItems.count) { _ in + collectionViewObserver.restoreSavedOffset() + + // Check if there are enough items. Otherwise ask for more + attemptBackPagination() } } func scrollToBottom(animated: Bool = false) { - tableViewObserver.scrollToBottom(animated: animated) + collectionViewObserver.scrollToBottom(animated: animated) } private func attemptBackPagination() { @@ -138,7 +139,7 @@ struct TimelineItemList: View { return } - if tableViewObserver.scrollViewTopVisiblePublisher.value == false { + if collectionViewObserver.scrollViewTopVisiblePublisher.value == false { return } diff --git a/ElementX/Sources/Screens/SessionVerification/SessionVerificationCoordinator.swift b/ElementX/Sources/Screens/SessionVerification/SessionVerificationCoordinator.swift index c3fa9df62..88f15b568 100644 --- a/ElementX/Sources/Screens/SessionVerification/SessionVerificationCoordinator.swift +++ b/ElementX/Sources/Screens/SessionVerification/SessionVerificationCoordinator.swift @@ -51,7 +51,7 @@ final class SessionVerificationCoordinator: Coordinator, Presentable { func start() { MXLog.debug("Did start.") sessionVerificationViewModel.callback = { [weak self] action in - guard let self = self else { return } + guard let self else { return } switch action { case .finished: diff --git a/ElementX/Sources/Screens/SessionVerification/SessionVerificationViewModel.swift b/ElementX/Sources/Screens/SessionVerification/SessionVerificationViewModel.swift index 89d3636c1..f63b0bec8 100644 --- a/ElementX/Sources/Screens/SessionVerification/SessionVerificationViewModel.swift +++ b/ElementX/Sources/Screens/SessionVerification/SessionVerificationViewModel.swift @@ -46,7 +46,7 @@ class SessionVerificationViewModel: SessionVerificationViewModelType, SessionVer sessionVerificationControllerProxy.callbacks .receive(on: DispatchQueue.main) .sink { [weak self] callback in - guard let self = self else { return } + guard let self else { return } switch callback { case .receivedVerificationData(let emojis): @@ -95,7 +95,7 @@ class SessionVerificationViewModel: SessionVerificationViewModelType, SessionVer private func setupStateMachine() { stateMachine.addTransitionHandler { [weak self] context in - guard let self = self else { return } + guard let self else { return } self.state.verificationState = context.toState diff --git a/ElementX/Sources/Screens/Settings/SettingsCoordinator.swift b/ElementX/Sources/Screens/Settings/SettingsCoordinator.swift index bfd3deec3..3687f8097 100644 --- a/ElementX/Sources/Screens/Settings/SettingsCoordinator.swift +++ b/ElementX/Sources/Screens/Settings/SettingsCoordinator.swift @@ -61,7 +61,7 @@ final class SettingsCoordinator: Coordinator, Presentable { indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: settingsHostingController) settingsViewModel.callback = { [weak self] result in - guard let self = self else { return } + guard let self else { return } MXLog.debug("SettingsViewModel did complete with result: \(result).") switch result { case .close: @@ -105,7 +105,7 @@ final class SettingsCoordinator: Coordinator, Presentable { screenshot: nil) let coordinator = BugReportCoordinator(parameters: params) coordinator.completion = { [weak self, weak coordinator] in - guard let self = self, let coordinator = coordinator else { return } + guard let self, let coordinator = coordinator else { return } self.parameters.navigationRouter.popModule(animated: true) self.remove(childCoordinator: coordinator) self.showSuccess(label: ElementL10n.done) @@ -114,7 +114,7 @@ final class SettingsCoordinator: Coordinator, Presentable { add(childCoordinator: coordinator) coordinator.start() navigationRouter.push(coordinator, animated: true) { [weak self] in - guard let self = self else { return } + guard let self else { return } self.remove(childCoordinator: coordinator) } diff --git a/ElementX/Sources/Screens/SplashScreen/SplashScreenCoordinator.swift b/ElementX/Sources/Screens/SplashScreen/SplashScreenCoordinator.swift index ff576cdf9..95b6181f1 100644 --- a/ElementX/Sources/Screens/SplashScreen/SplashScreenCoordinator.swift +++ b/ElementX/Sources/Screens/SplashScreen/SplashScreenCoordinator.swift @@ -50,7 +50,7 @@ final class SplashScreenCoordinator: Coordinator, Presentable { MXLog.debug("Did start.") splashScreenViewModel.callback = { [weak self] action in MXLog.debug("SplashScreenViewModel did complete with result: \(action).") - guard let self = self else { return } + guard let self else { return } switch action { case .login: self.callback?(.login) diff --git a/ElementX/Sources/Screens/SplashScreen/View/SplashScreen.swift b/ElementX/Sources/Screens/SplashScreen/View/SplashScreen.swift index 067f750cd..eac8878ec 100644 --- a/ElementX/Sources/Screens/SplashScreen/View/SplashScreen.swift +++ b/ElementX/Sources/Screens/SplashScreen/View/SplashScreen.swift @@ -136,7 +136,7 @@ struct SplashScreen: View { /// Stops the animation timer for manual interaction. private func stopTimer() { - guard let pageTimer = pageTimer else { return } + guard let pageTimer else { return } self.pageTimer = nil pageTimer.invalidate() diff --git a/ElementX/Sources/Services/Analytics/AnalyticsService.swift b/ElementX/Sources/Services/Analytics/AnalyticsService.swift index 2991a344a..fafe68c82 100644 --- a/ElementX/Sources/Services/Analytics/AnalyticsService.swift +++ b/ElementX/Sources/Services/Analytics/AnalyticsService.swift @@ -52,7 +52,7 @@ class AnalyticsService { return .failure(.accountDataFailure) case .success(let settings): // The id has already be set so we are done here. - if let settings = settings, settings.id != nil { + if let settings, settings.id != nil { return .success(settings) } diff --git a/ElementX/Sources/Services/Analytics/PostHogAnalyticsClient.swift b/ElementX/Sources/Services/Analytics/PostHogAnalyticsClient.swift index 1f2beb190..6a2f2381f 100644 --- a/ElementX/Sources/Services/Analytics/PostHogAnalyticsClient.swift +++ b/ElementX/Sources/Services/Analytics/PostHogAnalyticsClient.swift @@ -73,7 +73,7 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol { } func updateUserProperties(_ userProperties: AnalyticsEvent.UserProperties) { - guard let pendingUserProperties = pendingUserProperties else { + guard let pendingUserProperties else { pendingUserProperties = userProperties return } diff --git a/ElementX/Sources/Services/Authentication/OIDCService.swift b/ElementX/Sources/Services/Authentication/OIDCService.swift index 1177c297f..1b09f2f55 100644 --- a/ElementX/Sources/Services/Authentication/OIDCService.swift +++ b/ElementX/Sources/Services/Authentication/OIDCService.swift @@ -100,8 +100,8 @@ class OIDCService { additionalParameters: nil) let result: OIDAuthorizationResponse = try await withCheckedThrowingContinuation { continuation in self.session = OIDAuthorizationService.present(request, externalUserAgent: userAgent) { response, error in - guard let response = response else { - if let error = error { + guard let response else { + if let error { MXLog.info("User cancelled the ASWebAuthenticationSession window") continuation.resume(with: .failure(self.isUserCancellationError(error) ? OIDCError.userCancellation : error)) } else { @@ -136,7 +136,7 @@ extension OIDAuthorizationService { originalAuthorizationResponse authorizationResponse: OIDAuthorizationResponse?) async throws -> OIDTokenResponse { try await withCheckedThrowingContinuation { continuation in perform(request, originalAuthorizationResponse: authorizationResponse) { response, error in - guard let response = response else { + guard let response else { continuation.resume(with: .failure(error ?? OIDCError.unknown)) return } diff --git a/ElementX/Sources/Services/Background/UIKitBackgroundTask.swift b/ElementX/Sources/Services/Background/UIKitBackgroundTask.swift index ead6d27ce..14f99ad4b 100644 --- a/ElementX/Sources/Services/Background/UIKitBackgroundTask.swift +++ b/ElementX/Sources/Services/Background/UIKitBackgroundTask.swift @@ -52,7 +52,7 @@ class UIKitBackgroundTask: BackgroundTaskProtocol { // attempt to start identifier = application.beginBackgroundTask(withName: name) { [weak self] in - guard let self = self else { return } + guard let self else { return } self.expirationHandler?(self) } diff --git a/ElementX/Sources/Services/Background/UIKitBackgroundTaskService.swift b/ElementX/Sources/Services/Background/UIKitBackgroundTaskService.swift index d25d99523..0fae91954 100644 --- a/ElementX/Sources/Services/Background/UIKitBackgroundTaskService.swift +++ b/ElementX/Sources/Services/Background/UIKitBackgroundTaskService.swift @@ -31,7 +31,7 @@ class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol { func startBackgroundTask(withName name: String, isReusable: Bool, expirationHandler: (() -> Void)?) -> BackgroundTaskProtocol? { - guard let application = application else { + guard let application else { MXLog.verbose("Do not start background task: \(name). Application is nil") return nil } @@ -54,7 +54,7 @@ class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol { isReusable: isReusable, application: application, expirationHandler: { [weak self] task in - guard let self = self else { return } + guard let self else { return } self.reusableTasks[task.name] = nil expirationHandler?() }) { diff --git a/ElementX/Sources/Services/BugReport/ScreenshotDetector.swift b/ElementX/Sources/Services/BugReport/ScreenshotDetector.swift index 95997fecd..b5532bc84 100644 --- a/ElementX/Sources/Services/BugReport/ScreenshotDetector.swift +++ b/ElementX/Sources/Services/BugReport/ScreenshotDetector.swift @@ -69,7 +69,7 @@ class ScreenshotDetector { targetSize: PHImageManagerMaximumSize, contentMode: .default, options: PHImageRequestOptions.highQualitySyncLocal) { [weak self] image, _ in - guard let image = image else { + guard let image else { self?.fail(withError: ScreenshotDetectorError.loadFailed) return } diff --git a/ElementX/Sources/Services/Media/MediaProvider.swift b/ElementX/Sources/Services/Media/MediaProvider.swift index 82c64fcc0..69c6bfffa 100644 --- a/ElementX/Sources/Services/Media/MediaProvider.swift +++ b/ElementX/Sources/Services/Media/MediaProvider.swift @@ -31,7 +31,7 @@ struct MediaProvider: MediaProviderProtocol { } func imageFromSource(_ source: MediaSource?, avatarSize: AvatarSize?) -> UIImage? { - guard let source = source else { + guard let source else { return nil } let cacheKey = cacheKeyForURLString(source.underlyingSource.url(), avatarSize: avatarSize) @@ -39,7 +39,7 @@ struct MediaProvider: MediaProviderProtocol { } func imageFromURLString(_ urlString: String?, avatarSize: AvatarSize?) -> UIImage? { - guard let urlString = urlString else { + guard let urlString else { return nil } @@ -70,7 +70,7 @@ struct MediaProvider: MediaProviderProtocol { do { let imageData = try await Task.detached { () -> Data in - if let avatarSize = avatarSize { + if let avatarSize { return try await clientProxy.loadMediaThumbnailForSource(source.underlyingSource, width: UInt(avatarSize.scaledValue), height: UInt(avatarSize.scaledValue)) } else { return try await clientProxy.loadMediaContentForSource(source.underlyingSource) @@ -97,7 +97,7 @@ struct MediaProvider: MediaProviderProtocol { // MARK: - Private private func cacheKeyForURLString(_ urlString: String, avatarSize: AvatarSize?) -> String { - if let avatarSize = avatarSize { + if let avatarSize { return "\(urlString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}" } else { return urlString diff --git a/ElementX/Sources/Services/Media/MockMediaProvider.swift b/ElementX/Sources/Services/Media/MockMediaProvider.swift index 6137f58a8..09cbde9b0 100644 --- a/ElementX/Sources/Services/Media/MockMediaProvider.swift +++ b/ElementX/Sources/Services/Media/MockMediaProvider.swift @@ -31,7 +31,7 @@ struct MockMediaProvider: MediaProviderProtocol { return nil } - if let avatarSize = avatarSize { + if let avatarSize { switch avatarSize { case .room: return Asset.Images.appLogo.image diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index 096e3a137..679a8764c 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -128,7 +128,7 @@ class RoomProxy: RoomProxyProtocol { } func loadDisplayName() async -> Result { - if let displayName = displayName { return .success(displayName) } + if let displayName { return .success(displayName) } do { let displayName = try await Task.dispatch(on: .global()) { @@ -176,7 +176,7 @@ class RoomProxy: RoomProxyProtocol { return await Task.dispatch(on: .global()) { do { - if let inReplyToEventId = inReplyToEventId { + if let inReplyToEventId { try self.room.sendReply(msg: message, inReplyToEventId: inReplyToEventId, txnId: transactionId) } else { let messageContent = messageEventContentFromMarkdown(md: message) diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift index dbd728dfb..6b0f0282f 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift @@ -54,7 +54,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol { .callbacks .receive(on: DispatchQueue.main) .sink { [weak self] callback in - guard let self = self else { return } + guard let self else { return } switch callback { case .updatedMessages: @@ -248,7 +248,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol { switch await roomProxy.loadAvatarURLForUserId(timelineItem.senderId) { case .success(let avatarURLString): - guard let avatarURLString = avatarURLString else { + guard let avatarURLString else { return } @@ -278,7 +278,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol { switch await roomProxy.loadDisplayNameForUserId(timelineItem.senderId) { case .success(let displayName): - guard let displayName = displayName, + guard let displayName, let index = timelineItems.firstIndex(where: { $0.id == timelineItem.id }), var item = timelineItems[index] as? EventBasedTimelineItemProtocol else { return diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift b/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift index f9daee6cc..6b84981f5 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift @@ -106,34 +106,34 @@ private extension RoomTimelineProvider { } private func replaceItems(_ items: [MatrixRustSDK.TimelineItem]?) { - guard let items = items else { return } + guard let items else { return } itemProxies = items.map(TimelineItemProxy.init) } private func insertItem(_ data: InsertAtData?) { - guard let data = data else { return } + guard let data else { return } let itemProxy = TimelineItemProxy(item: data.item) itemProxies.insert(itemProxy, at: Int(data.index)) } private func updateItem(_ data: UpdateAtData?) { - guard let data = data else { return } + guard let data else { return } let itemProxy = TimelineItemProxy(item: data.item) itemProxies[Int(data.index)] = itemProxy } private func removeItem(at index: UInt32?) { - guard let index = index else { return } + guard let index else { return } itemProxies.remove(at: Int(index)) } private func moveItem(_ data: MoveData?) { - guard let data = data else { return } + guard let data else { return } itemProxies.move(fromOffsets: IndexSet(integer: Int(data.oldIndex)), toOffset: Int(data.newIndex)) } private func pushItem(_ item: MatrixRustSDK.TimelineItem?) { - guard let item = item else { return } + guard let item else { return } itemProxies.append(TimelineItemProxy(item: item)) } diff --git a/ElementX/Sources/UserSession/UserSessionFlowCoordinator.swift b/ElementX/Sources/UserSession/UserSessionFlowCoordinator.swift index fdc9885ad..d82c376ed 100644 --- a/ElementX/Sources/UserSession/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/UserSession/UserSessionFlowCoordinator.swift @@ -50,7 +50,7 @@ class UserSessionFlowCoordinator: Coordinator { private func setupStateMachine() { stateMachine.addTransitionHandler { [weak self] context in - guard let self = self else { return } + guard let self else { return } switch (context.fromState, context.event, context.toState) { case (.initial, .start, .homeScreen): @@ -89,7 +89,7 @@ class UserSessionFlowCoordinator: Coordinator { let coordinator = HomeScreenCoordinator(parameters: parameters) coordinator.callback = { [weak self] action in - guard let self = self else { return } + guard let self else { return } switch action { case .presentRoom(let roomIdentifier): @@ -142,7 +142,7 @@ class UserSessionFlowCoordinator: Coordinator { add(childCoordinator: coordinator) navigationRouter.push(coordinator) { [weak self] in - guard let self = self else { return } + guard let self else { return } self.stateMachine.processEvent(.dismissedRoomScreen) } } @@ -166,7 +166,7 @@ class UserSessionFlowCoordinator: Coordinator { bugReportService: bugReportService) let coordinator = SettingsCoordinator(parameters: parameters) coordinator.callback = { [weak self] action in - guard let self = self else { return } + guard let self else { return } switch action { case .dismiss: self.dismissSettingsScreen() @@ -246,7 +246,7 @@ class UserSessionFlowCoordinator: Coordinator { screenshot: image) let coordinator = BugReportCoordinator(parameters: parameters) coordinator.completion = { [weak self, weak coordinator] in - guard let self = self, let coordinator = coordinator else { return } + guard let self, let coordinator = coordinator else { return } self.navigationRouter.dismissModule(animated: true) self.remove(childCoordinator: coordinator) } diff --git a/ElementX/SupportingFiles/target.yml b/ElementX/SupportingFiles/target.yml index e671055b5..33b125341 100644 --- a/ElementX/SupportingFiles/target.yml +++ b/ElementX/SupportingFiles/target.yml @@ -62,6 +62,7 @@ targets: preBuildScripts: - name: 🛠 SwiftGen runOnlyWhenInstalling: false + basedOnDependencyAnalysis: false shell: /bin/sh script: | export PATH="$PATH:/opt/homebrew/bin" @@ -74,6 +75,7 @@ targets: postBuildScripts: - name: ⚠️ SwiftLint runOnlyWhenInstalling: false + basedOnDependencyAnalysis: false shell: /bin/sh script: | export PATH="$PATH:/opt/homebrew/bin" @@ -84,6 +86,7 @@ targets: fi - name: 🧹 SwiftFormat runOnlyWhenInstalling: false + basedOnDependencyAnalysis: false shell: /bin/sh script: | export PATH="$PATH:/opt/homebrew/bin" diff --git a/IntegrationTests/Sources/TestMeasurementParser.swift b/IntegrationTests/Sources/TestMeasurementParser.swift index a649e0b3c..0e43cc05c 100644 --- a/IntegrationTests/Sources/TestMeasurementParser.swift +++ b/IntegrationTests/Sources/TestMeasurementParser.swift @@ -84,7 +84,7 @@ class TestMeasurementParser { dup2(pipe.fileHandleForWriting.fileDescriptor, STDERR_FILENO) pipe.fileHandleForReading.readabilityHandler = { [weak self] handle in - guard let self = self else { + guard let self else { return } diff --git a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateCoordinator.swift b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateCoordinator.swift index b8b53d35a..5f1725dfc 100644 --- a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateCoordinator.swift +++ b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateCoordinator.swift @@ -61,7 +61,7 @@ final class TemplateCoordinator: Coordinator, Presentable { func start() { MXLog.debug("Did start.") templateViewModel.callback = { [weak self] action in - guard let self = self else { return } + guard let self else { return } MXLog.debug("TemplateViewModel did complete with result: \(action).") switch action { case .accept: diff --git a/UITests/Sources/Application.swift b/UITests/Sources/Application.swift index 42643d651..e7beafe99 100644 --- a/UITests/Sources/Application.swift +++ b/UITests/Sources/Application.swift @@ -47,7 +47,7 @@ extension XCUIApplication { named: identifier.rawValue, testName: testName) - if let failure = failure, + if let failure, !failure.contains("No reference was found on disk."), !failure.contains("to test against the newly-recorded snapshot") { XCTFail(failure) diff --git a/UnitTests/Sources/AttributedStringBuilderTests.swift b/UnitTests/Sources/AttributedStringBuilderTests.swift index b20de1a47..bd56289ae 100644 --- a/UnitTests/Sources/AttributedStringBuilderTests.swift +++ b/UnitTests/Sources/AttributedStringBuilderTests.swift @@ -367,7 +367,7 @@ class AttributedStringBuilderTests: XCTestCase { // MARK: - Private private func checkMatrixEntityLinkIn(attributedString: AttributedString?, expected: String) { - guard let attributedString = attributedString else { + guard let attributedString else { XCTFail("Could not build the attributed string") return } diff --git a/UnitTests/Sources/ScreenshotDetectorTests.swift b/UnitTests/Sources/ScreenshotDetectorTests.swift index bb7d4110a..40c530e4f 100644 --- a/UnitTests/Sources/ScreenshotDetectorTests.swift +++ b/UnitTests/Sources/ScreenshotDetectorTests.swift @@ -45,7 +45,7 @@ class ScreenshotDetectorTests: XCTestCase { XCTAssertNil(image) // and get an error - guard let error = error else { + guard let error else { XCTFail("Should get an error") return } @@ -62,7 +62,7 @@ class ScreenshotDetectorTests: XCTestCase { XCTAssertNil(image) // and get an error - guard let error = error else { + guard let error else { XCTFail("Should get an error") return } diff --git a/UnitTests/Sources/SessionVerificationViewModelTests.swift b/UnitTests/Sources/SessionVerificationViewModelTests.swift index 672d994e6..dc463e17a 100644 --- a/UnitTests/Sources/SessionVerificationViewModelTests.swift +++ b/UnitTests/Sources/SessionVerificationViewModelTests.swift @@ -42,7 +42,7 @@ class SessionVerificationViewModelTests: XCTestCase { XCTAssertEqual(context.viewState.verificationState, .requestingVerification) } - func testVerificationCancellation() async { + func testVerificationCancellation() async throws { XCTAssertEqual(context.viewState.verificationState, .initial) context.send(viewAction: .start) @@ -53,7 +53,7 @@ class SessionVerificationViewModelTests: XCTestCase { XCTAssertEqual(context.viewState.verificationState, .cancelling) - await Task.yield() + try await Task.sleep(nanoseconds: 100_000_000) XCTAssertEqual(context.viewState.verificationState, .cancelled) diff --git a/changelog.d/163.change b/changelog.d/163.change new file mode 100644 index 000000000..1256ecd65 --- /dev/null +++ b/changelog.d/163.change @@ -0,0 +1 @@ +Build with Xcode 14.0 and fix introspection on the timeline List. diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 28d569d1a..f9c4ff562 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -2,7 +2,7 @@ require 'yaml' require_relative 'changelog' before_all do - xcversion(version: "~> 13.4") + xcversion(version: "~> 14.0") end lane :alpha do diff --git a/project.yml b/project.yml index cd0264eb0..ccc54e65f 100644 --- a/project.yml +++ b/project.yml @@ -4,14 +4,13 @@ attributes: fileGroups: - project.yml - - changelog.d options: groupSortPosition: bottom createIntermediateGroups: true deploymentTarget: - iOS: "15.0" - macOS: "12.0" + iOS: "16.0" + macOS: "13.0" groupOrdering: - order: [ElementX, UnitTests, UITests, IntegrationTests, Tools] - pattern: ElementX