Update files following swiftformat upgrade

This commit is contained in:
Stefan Ceriu
2026-01-27 08:47:03 +02:00
committed by Stefan Ceriu
parent 2bb26efbe1
commit 04053ae69b
343 changed files with 1502 additions and 1048 deletions

View File

@@ -11,7 +11,7 @@ SwiftLint.lint(.modifiedAndCreatedFiles(directory: nil),
let danger = Danger() let danger = Danger()
// All of the new and modified files together. /// All of the new and modified files together.
let editedFiles = danger.git.modifiedFiles + danger.git.createdFiles let editedFiles = danger.git.modifiedFiles + danger.git.createdFiles
// Warn when there is a big PR // Warn when there is a big PR
@@ -24,7 +24,7 @@ if danger.github.pullRequest.body?.isEmpty ?? true {
warn("Please provide a description for this PR.") warn("Please provide a description for this PR.")
} }
// Check for screenshots on view changes /// Check for screenshots on view changes
let hasChangedViews = !editedFiles.filter { $0.lowercased().contains("/view") }.isEmpty let hasChangedViews = !editedFiles.filter { $0.lowercased().contains("/view") }.isEmpty
if hasChangedViews { if hasChangedViews {
if (danger.github.pullRequest.body?.contains("user-attachments") ?? false) == false { if (danger.github.pullRequest.body?.contains("user-attachments") ?? false) == false {
@@ -32,7 +32,7 @@ if hasChangedViews {
} }
} }
// Check for pngs on resources /// Check for pngs on resources
let hasPngs = !editedFiles.filter { $0.lowercased().contains(".xcassets") && $0.lowercased().hasSuffix(".png") }.isEmpty let hasPngs = !editedFiles.filter { $0.lowercased().contains(".xcassets") && $0.lowercased().hasSuffix(".png") }.isEmpty
if hasPngs { if hasPngs {
warn("You seem to have made changes to some resource images. Please consider using an SVG or PDF.") warn("You seem to have made changes to some resource images. Please consider using an SVG or PDF.")

View File

@@ -118,7 +118,9 @@ struct PreviewsWrapperView: View {
private let name: String private let name: String
private let previews: [_Preview] private let previews: [_Preview]
private(set) var currentIndex = -1 private(set) var currentIndex = -1
var currentPreview: _Preview { previews[currentIndex] } var currentPreview: _Preview {
previews[currentIndex]
}
private(set) var isDone = false private(set) var isDone = false

View File

@@ -13,5 +13,7 @@ protocol AppSettingsHookProtocol {
} }
struct DefaultAppSettingsHook: AppSettingsHookProtocol { struct DefaultAppSettingsHook: AppSettingsHookProtocol {
func configure(_ appSettings: AppSettings) -> AppSettings { appSettings } func configure(_ appSettings: AppSettings) -> AppSettings {
appSettings
}
} }

View File

@@ -13,5 +13,7 @@ protocol BugReportHookProtocol {
} }
struct DefaultBugReportHook: BugReportHookProtocol { struct DefaultBugReportHook: BugReportHookProtocol {
func update(_ bugReport: BugReport) -> BugReport { bugReport } func update(_ bugReport: BugReport) -> BugReport {
bugReport
}
} }

View File

@@ -13,5 +13,7 @@ protocol ClientBuilderHookProtocol {
} }
struct DefaultClientBuilderHook: ClientBuilderHookProtocol { struct DefaultClientBuilderHook: ClientBuilderHookProtocol {
func configure(_ builder: ClientBuilder) -> ClientBuilder { builder } func configure(_ builder: ClientBuilder) -> ClientBuilder {
builder
}
} }

View File

@@ -12,5 +12,7 @@ protocol DeveloperOptionsScreenHookProtocol {
} }
struct DefaultDeveloperOptionsScreenHook: DeveloperOptionsScreenHookProtocol { struct DefaultDeveloperOptionsScreenHook: DeveloperOptionsScreenHookProtocol {
func generalSectionRows() -> AnyView? { nil } func generalSectionRows() -> AnyView? {
nil
}
} }

View File

@@ -15,5 +15,7 @@ protocol RoomScreenHookProtocol {
struct DefaultRoomScreenHook: RoomScreenHookProtocol { struct DefaultRoomScreenHook: RoomScreenHookProtocol {
func configure(with userSession: UserSessionProtocol?) async { } func configure(with userSession: UserSessionProtocol?) async { }
func update(_ viewState: RoomScreenViewState) -> RoomScreenViewState { viewState } func update(_ viewState: RoomScreenViewState) -> RoomScreenViewState {
viewState
}
} }

View File

@@ -198,18 +198,16 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
} }
func toPresentable() -> AnyView { func toPresentable() -> AnyView {
AnyView( AnyView(navigationRootCoordinator.toPresentable()
navigationRootCoordinator.toPresentable() .environment(\.analyticsService, ServiceLocator.shared.analytics)
.environment(\.analyticsService, ServiceLocator.shared.analytics) .onReceive(appSettings.$appAppearance) { [weak self] appAppearance in
.onReceive(appSettings.$appAppearance) { [weak self] appAppearance in guard let self else { return }
guard let self else { return }
windowManager.windows.forEach { window in windowManager.windows.forEach { window in
// Unfortunately .preferredColorScheme doesn't propagate properly throughout the app when changed // Unfortunately .preferredColorScheme doesn't propagate properly throughout the app when changed
window.overrideUserInterfaceStyle = appAppearance.interfaceStyle window.overrideUserInterfaceStyle = appAppearance.interfaceStyle
}
} }
) })
} }
func handlePotentialPhishingAttempt(url: URL, openURLAction: @escaping (URL) -> Void) -> Bool { func handlePotentialPhishingAttempt(url: URL, openURLAction: @escaping (URL) -> Void) -> Bool {
@@ -450,7 +448,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
userSessionMigrationsOldVersion = nil userSessionMigrationsOldVersion = nil
} }
// This could be removed once the adoption of 25.06.x is widespread. /// This could be removed once the adoption of 25.06.x is widespread.
private func performSettingsToAccountDataMigration(userSession: UserSessionProtocol) { private func performSettingsToAccountDataMigration(userSession: UserSessionProtocol) {
guard let userDefaults = UserDefaults(suiteName: InfoPlistReader.main.appGroupIdentifier) else { guard let userDefaults = UserDefaults(suiteName: InfoPlistReader.main.appGroupIdentifier) else {
return return
@@ -1200,12 +1198,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
// This is important for the app to keep refreshing in the background // This is important for the app to keep refreshing in the background
scheduleBackgroundAppRefresh() scheduleBackgroundAppRefresh()
/// We have a lot of crashes stemming here which we previously believed are caused by stopSync not being async // We have a lot of crashes stemming here which we previously believed are caused by stopSync not being async
/// on the client proxy side (see the comment on that method). We have now realised that will likely not fix anything but // on the client proxy side (see the comment on that method). We have now realised that will likely not fix anything but
/// we also noticed this does not crash on the main thread, even though the whole AppCoordinator is on the Main actor. // we also noticed this does not crash on the main thread, even though the whole AppCoordinator is on the Main actor.
/// As such, we introduced a MainActor conformance on the expirationHandler but we are also assuming main actor // As such, we introduced a MainActor conformance on the expirationHandler but we are also assuming main actor
/// isolated in the `stopSync` method above. // isolated in the `stopSync` method above.
/// https://sentry.tools.element.io/organizations/element/issues/4477794/ // https://sentry.tools.element.io/organizations/element/issues/4477794/
task.expirationHandler = { @Sendable [weak self] in task.expirationHandler = { @Sendable [weak self] in
MXLog.info("Background app refresh task is about to expire.") MXLog.info("Background app refresh task is about to expire.")

View File

@@ -18,7 +18,7 @@ class AppMediator: AppMediatorProtocol {
self.networkMonitor = networkMonitor self.networkMonitor = networkMonitor
} }
// UIApplication.State won't update if we store this e.g. in the constructor /// UIApplication.State won't update if we store this e.g. in the constructor
private var application: UIApplication { private var application: UIApplication {
UIApplication.shared UIApplication.shared
} }

View File

@@ -41,6 +41,11 @@ struct CommonFlowParameters {
let notificationManager: NotificationManagerProtocol let notificationManager: NotificationManagerProtocol
let stateMachineFactory: StateMachineFactoryProtocol let stateMachineFactory: StateMachineFactoryProtocol
var windowManager: WindowManagerProtocol { appMediator.windowManager } var windowManager: WindowManagerProtocol {
var ongoingCallRoomIDPublisher: CurrentValuePublisher<String?, Never> { elementCallService.ongoingCallRoomIDPublisher } appMediator.windowManager
}
var ongoingCallRoomIDPublisher: CurrentValuePublisher<String?, Never> {
elementCallService.ongoingCallRoomIDPublisher
}
} }

View File

@@ -57,8 +57,13 @@ import SwiftUI
let module: NavigationModule let module: NavigationModule
let details: TabDetails let details: TabDetails
var id: ObjectIdentifier { module.id } var id: ObjectIdentifier {
@MainActor var coordinator: CoordinatorProtocol? { module.coordinator } module.id
}
@MainActor var coordinator: CoordinatorProtocol? {
module.coordinator
}
} }
fileprivate var tabModules = [TabModule]() { fileprivate var tabModules = [TabModule]() {

View File

@@ -13,7 +13,7 @@ import EmbeddedElementCall
import Foundation import Foundation
import SwiftUI import SwiftUI
// Common settings between app and NSE /// Common settings between app and NSE
protocol CommonSettingsProtocol { protocol CommonSettingsProtocol {
var logLevel: LogLevel { get } var logLevel: LogLevel { get }
var traceLogPacks: Set<TraceLogPack> { get } var traceLogPacks: Set<TraceLogPack> { get }
@@ -265,7 +265,9 @@ final class AppSettings {
} }
private(set) var pushGatewayBaseURL: URL = "https://matrix.org" private(set) var pushGatewayBaseURL: URL = "https://matrix.org"
var pushGatewayNotifyEndpoint: URL { pushGatewayBaseURL.appending(path: "_matrix/push/v1/notify") } var pushGatewayNotifyEndpoint: URL {
pushGatewayBaseURL.appending(path: "_matrix/push/v1/notify")
}
@UserPreference(key: UserDefaultsKeys.enableNotifications, defaultValue: true, storageType: .userDefaults(store)) @UserPreference(key: UserDefaultsKeys.enableNotifications, defaultValue: true, storageType: .userDefaults(store))
var enableNotifications var enableNotifications
@@ -305,7 +307,9 @@ final class AppSettings {
/// The URL to open with more information about analytics terms. When this is `nil` the "Learn more" link will be hidden. /// The URL to open with more information about analytics terms. When this is `nil` the "Learn more" link will be hidden.
private(set) var analyticsTermsURL: URL? = "https://element.io/cookie-policy" private(set) var analyticsTermsURL: URL? = "https://element.io/cookie-policy"
/// Whether or not there the app is able ask for user consent to enable analytics or sentry reporting. /// Whether or not there the app is able ask for user consent to enable analytics or sentry reporting.
var canPromptForAnalytics: Bool { analyticsConfiguration != nil || bugReportSentryURL != nil } var canPromptForAnalytics: Bool {
analyticsConfiguration != nil || bugReportSentryURL != nil
}
private static func makeAnalyticsConfiguration() -> AnalyticsConfiguration? { private static func makeAnalyticsConfiguration() -> AnalyticsConfiguration? {
guard let host = Secrets.postHogHost, let apiKey = Secrets.postHogAPIKey else { return nil } guard let host = Secrets.postHogHost, let apiKey = Secrets.postHogAPIKey else { return nil }
@@ -364,7 +368,7 @@ final class AppSettings {
// MARK: - Maps // MARK: - Maps
// maptiler base url /// maptiler base url
private(set) var mapTilerConfiguration = MapTilerConfiguration(baseURL: "https://api.maptiler.com/maps", private(set) var mapTilerConfiguration = MapTilerConfiguration(baseURL: "https://api.maptiler.com/maps",
apiKey: Secrets.mapLibreAPIKey, apiKey: Secrets.mapLibreAPIKey,
lightStyleID: "9bc819c8-e627-474a-a348-ec144fe3d810", lightStyleID: "9bc819c8-e627-474a-a348-ec144fe3d810",
@@ -377,14 +381,14 @@ final class AppSettings {
// MARK: - Feature Flags // MARK: - Feature Flags
// Spaces /// Spaces
@UserPreference(key: UserDefaultsKeys.spaceSettingsEnabled, defaultValue: false, storageType: .userDefaults(store)) @UserPreference(key: UserDefaultsKeys.spaceSettingsEnabled, defaultValue: false, storageType: .userDefaults(store))
var spaceSettingsEnabled var spaceSettingsEnabled
@UserPreference(key: UserDefaultsKeys.createSpaceEnabled, defaultValue: false, storageType: .userDefaults(store)) @UserPreference(key: UserDefaultsKeys.createSpaceEnabled, defaultValue: false, storageType: .userDefaults(store))
var createSpaceEnabled var createSpaceEnabled
// Others /// Others
@UserPreference(key: UserDefaultsKeys.publicSearchEnabled, defaultValue: false, storageType: .userDefaults(store)) @UserPreference(key: UserDefaultsKeys.publicSearchEnabled, defaultValue: false, storageType: .userDefaults(store))
var publicSearchEnabled var publicSearchEnabled

View File

@@ -22,5 +22,7 @@ struct MapTilerConfiguration {
/// A MapLibre style ID for a dark-mode map. /// A MapLibre style ID for a dark-mode map.
let darkStyleID: String let darkStyleID: String
var isEnabled: Bool { apiKey != nil } var isEnabled: Bool {
apiKey != nil
}
} }

View File

@@ -16,8 +16,13 @@ import Combine
class RemotePreference<T: Equatable> { class RemotePreference<T: Equatable> {
private let defaultValue: T private let defaultValue: T
private let subject: CurrentValueSubject<T, Never> private let subject: CurrentValueSubject<T, Never>
var publisher: CurrentValuePublisher<T, Never> { subject.asCurrentValuePublisher() } var publisher: CurrentValuePublisher<T, Never> {
var isRemotelyConfigured: Bool { subject.value != defaultValue } subject.asCurrentValuePublisher()
}
var isRemotelyConfigured: Bool {
subject.value != defaultValue
}
init(_ defaultValue: T) { init(_ defaultValue: T) {
self.defaultValue = defaultValue self.defaultValue = defaultValue

View File

@@ -24,14 +24,17 @@ final class UserPreference<T: Codable> {
} }
private let key: String private let key: String
private var remoteKey: String { "\(Self.remotePrefix)\(key)" } private var remoteKey: String {
"\(Self.remotePrefix)\(key)"
}
private var keyedStorage: any KeyedStorage<T> private var keyedStorage: any KeyedStorage<T>
private let defaultValue: T private let defaultValue: T
private let subject: PassthroughSubject<T, Never> = .init() private let subject: PassthroughSubject<T, Never> = .init()
private let mode: Mode private let mode: Mode
// This can be used to check if is still possible for the user to change the value or not /// This can be used to check if is still possible for the user to change the value or not
// Can only be accessed by using `_preferenceName.isLockedToRemote` /// Can only be accessed by using `_preferenceName.isLockedToRemote`
var isLockedToRemote: Bool { var isLockedToRemote: Bool {
mode == .remoteOverLocal && remoteValue != nil mode == .remoteOverLocal && remoteValue != nil
} }
@@ -53,7 +56,7 @@ final class UserPreference<T: Codable> {
self.mode = mode self.mode = mode
} }
// The wrapped value is supposed to be the one updated by the user so it can only control the local value /// The wrapped value is supposed to be the one updated by the user so it can only control the local value
var wrappedValue: T { var wrappedValue: T {
get { get {
switch mode { switch mode {
@@ -69,8 +72,8 @@ final class UserPreference<T: Codable> {
} }
} }
// This is supposed to be the value that is set by the remote settings /// This is supposed to be the value that is set by the remote settings
// So it can only be accessed by doing `AppSettings._preferenceName.remoteValue` /// So it can only be accessed by doing `AppSettings._preferenceName.remoteValue`
var remoteValue: T? { var remoteValue: T? {
get { get {
keyedStorage[remoteKey] keyedStorage[remoteKey]

View File

@@ -25,7 +25,9 @@ class ChatsTabFlowCoordinator: FlowCoordinatorProtocol {
private let navigationSplitCoordinator: NavigationSplitCoordinator private let navigationSplitCoordinator: NavigationSplitCoordinator
private let flowParameters: CommonFlowParameters private let flowParameters: CommonFlowParameters
private var userSession: UserSessionProtocol { flowParameters.userSession } private var userSession: UserSessionProtocol {
flowParameters.userSession
}
private let stateMachine: ChatsTabFlowCoordinatorStateMachine private let stateMachine: ChatsTabFlowCoordinatorStateMachine

View File

@@ -20,7 +20,9 @@ class MediaEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
private let navigationStackCoordinator: NavigationStackCoordinator private let navigationStackCoordinator: NavigationStackCoordinator
private let flowParameters: CommonFlowParameters private let flowParameters: CommonFlowParameters
private var userSession: UserSessionProtocol { flowParameters.userSession } private var userSession: UserSessionProtocol {
flowParameters.userSession
}
private let actionsSubject: PassthroughSubject<MediaEventsTimelineFlowCoordinatorAction, Never> = .init() private let actionsSubject: PassthroughSubject<MediaEventsTimelineFlowCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<MediaEventsTimelineFlowCoordinatorAction, Never> { var actionsPublisher: AnyPublisher<MediaEventsTimelineFlowCoordinatorAction, Never> {

View File

@@ -21,7 +21,9 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
private let navigationStackCoordinator: NavigationStackCoordinator private let navigationStackCoordinator: NavigationStackCoordinator
private let flowParameters: CommonFlowParameters private let flowParameters: CommonFlowParameters
private var userSession: UserSessionProtocol { flowParameters.userSession } private var userSession: UserSessionProtocol {
flowParameters.userSession
}
private let actionsSubject: PassthroughSubject<PinnedEventsTimelineFlowCoordinatorAction, Never> = .init() private let actionsSubject: PassthroughSubject<PinnedEventsTimelineFlowCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<PinnedEventsTimelineFlowCoordinatorAction, Never> { var actionsPublisher: AnyPublisher<PinnedEventsTimelineFlowCoordinatorAction, Never> {

View File

@@ -67,7 +67,9 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
private let navigationStackCoordinator: NavigationStackCoordinator private let navigationStackCoordinator: NavigationStackCoordinator
private let flowParameters: CommonFlowParameters private let flowParameters: CommonFlowParameters
private var userSession: UserSessionProtocol { flowParameters.userSession } private var userSession: UserSessionProtocol {
flowParameters.userSession
}
private var roomProxy: JoinedRoomProxyProtocol! private var roomProxy: JoinedRoomProxyProtocol!

View File

@@ -28,7 +28,7 @@ final class SpaceSettingsFlowCoordinator: FlowCoordinatorProtocol {
/// The edit address screen /// The edit address screen
case editAddress case editAddress
// Other flows /// Other flows
/// The roles and permissions screen /// The roles and permissions screen
case rolesAndPermissionsFlow case rolesAndPermissionsFlow
/// The members flow screen /// The members flow screen

View File

@@ -27,7 +27,9 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
private let appLockService: AppLockServiceProtocol private let appLockService: AppLockServiceProtocol
private let flowParameters: CommonFlowParameters private let flowParameters: CommonFlowParameters
private var userSession: UserSessionProtocol { flowParameters.userSession } private var userSession: UserSessionProtocol {
flowParameters.userSession
}
private let onboardingFlowCoordinator: OnboardingFlowCoordinator private let onboardingFlowCoordinator: OnboardingFlowCoordinator
private let onboardingStackCoordinator: NavigationStackCoordinator private let onboardingStackCoordinator: NavigationStackCoordinator
@@ -145,12 +147,12 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
chatsTabFlowCoordinator.clearRoute(animated: animated) chatsTabFlowCoordinator.clearRoute(animated: animated)
} }
// Clearing routes is more complicated than it first seems. When passing routes /// Clearing routes is more complicated than it first seems. When passing routes
// to the chats flow we can't clear all routes as e.g. childRoom/childEvent etc /// to the chats flow we can't clear all routes as e.g. childRoom/childEvent etc
// expect to push into the existing stack. But we do need to hide any sheets that /// expect to push into the existing stack. But we do need to hide any sheets that
// might cover up the presented route. BUT! We probably shouldn't dismiss onboarding /// might cover up the presented route. BUT! We probably shouldn't dismiss onboarding
// or verification flows until they're complete This needs more thought before we /// or verification flows until they're complete This needs more thought before we
// codify it all into the state machine. /// codify it all into the state machine.
private func clearPresentedSheets(animated: Bool) { private func clearPresentedSheets(animated: Bool) {
switch stateMachine.state { switch stateMachine.state {
case .initial, .tabBar: case .initial, .tabBar:

View File

@@ -38,7 +38,7 @@ extension RoomMemberProxyMock {
powerLevel = configuration.powerLevel powerLevel = configuration.powerLevel
} }
// Mocks /// Mocks
static var mockMe: RoomMemberProxyMock { static var mockMe: RoomMemberProxyMock {
RoomMemberProxyMock(with: .init(userID: "@me:matrix.org", RoomMemberProxyMock(with: .init(userID: "@me:matrix.org",
displayName: "Me", displayName: "Me",

View File

@@ -9,7 +9,7 @@
import Foundation import Foundation
extension UserProfileProxy { extension UserProfileProxy {
// Mocks /// Mocks
static var mockAlice: UserProfileProxy { static var mockAlice: UserProfileProxy {
.init(userID: "@alice:matrix.org", displayName: "Alice", avatarURL: "mxc://matrix.org/UcCimidcvpFvWkPzvjXMQPHA") .init(userID: "@alice:matrix.org", displayName: "Alice", avatarURL: "mxc://matrix.org/UcCimidcvpFvWkPzvjXMQPHA")
} }

View File

@@ -56,7 +56,9 @@ enum A11yIdentifiers {
} }
struct AppLockScreen { struct AppLockScreen {
func numpad(_ digit: Int) -> String { "app_lock-numpad_\(digit)" } func numpad(_ digit: Int) -> String {
"app_lock-numpad_\(digit)"
}
} }
struct AppLockSetupBiometricsScreen { struct AppLockSetupBiometricsScreen {

View File

@@ -15,7 +15,6 @@ struct ScreenTrackerViewModifier: ViewModifier {
let screen: AnalyticsEvent.MobileScreen.ScreenName let screen: AnalyticsEvent.MobileScreen.ScreenName
@ViewBuilder
func body(content: Content) -> some View { func body(content: Content) -> some View {
content content
.onAppear { .onAppear {

View File

@@ -13,7 +13,7 @@ enum Avatars {
enum Size { enum Size {
case user(on: UserAvatarSizeOnScreen) case user(on: UserAvatarSizeOnScreen)
case room(on: RoomAvatarSizeOnScreen) case room(on: RoomAvatarSizeOnScreen)
// custom /// custom
case custom(CGFloat) case custom(CGFloat)
/// Value in UIKit points /// Value in UIKit points

View File

@@ -102,11 +102,9 @@ private func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float
let quantG = (value / 19) % 19 let quantG = (value / 19) % 19
let quantB = value % 19 let quantB = value % 19
let rgb = (signPow((Float(quantR) - 9) / 9, 2) * maximumValue, return (signPow((Float(quantR) - 9) / 9, 2) * maximumValue,
signPow((Float(quantG) - 9) / 9, 2) * maximumValue, signPow((Float(quantG) - 9) / 9, 2) * maximumValue,
signPow((Float(quantB) - 9) / 9, 2) * maximumValue) signPow((Float(quantB) - 9) / 9, 2) * maximumValue)
return rgb
} }
private func signPow(_ value: Float, _ exp: Float) -> Float { private func signPow(_ value: Float, _ exp: Float) -> Float {

View File

@@ -39,14 +39,12 @@ struct CollapsibleReactionLayout: Layout {
rows: collapsedRows, rows: collapsedRows,
collapseButton: subviewsByType.collapseButton, collapseButton: subviewsByType.collapseButton,
addMoreButton: subviewsByType.addMoreButton) addMoreButton: subviewsByType.addMoreButton)
let size = sizeThatFits(rows: collapsedRowsWithButtons) return sizeThatFits(rows: collapsedRowsWithButtons)
return size
} else { } else {
// Show all subviews with the button at the end // Show all subviews with the button at the end
var rowsWithButtons = calculateRows(proposal: proposal, subviews: Array(subviews)) var rowsWithButtons = calculateRows(proposal: proposal, subviews: Array(subviews))
ensureCollapseAndAddMoreButtonsAreOnTheSameRow(&rowsWithButtons) ensureCollapseAndAddMoreButtonsAreOnTheSameRow(&rowsWithButtons)
let size = sizeThatFits(rows: rowsWithButtons) return sizeThatFits(rows: rowsWithButtons)
return size
} }
} else { } else {
// Otherwise we are just calculating the size of all items without the button // Otherwise we are just calculating the size of all items without the button

View File

@@ -21,7 +21,7 @@ struct CurrentValuePublisher<Output, Failure: Error>: Publisher {
self.init(CurrentValueSubject(value)) self.init(CurrentValueSubject(value))
} }
func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input { func receive<S: Subscriber>(subscriber: S) where Failure == S.Failure, Output == S.Input {
subject.receive(subscriber: subscriber) subject.receive(subscriber: subscriber)
} }

View File

@@ -13,7 +13,7 @@ protocol AlertProtocol {
} }
extension View { extension View {
func alert<Item, Actions, Message>(item: Binding<Item?>, @ViewBuilder actions: (Item) -> Actions, @ViewBuilder message: (Item) -> Message) -> some View where Item: AlertProtocol, Actions: View, Message: View { func alert<Item: AlertProtocol, Actions: View, Message: View>(item: Binding<Item?>, @ViewBuilder actions: (Item) -> Actions, @ViewBuilder message: (Item) -> Message) -> some View {
let binding = Binding<Bool>(get: { let binding = Binding<Bool>(get: {
item.wrappedValue != nil item.wrappedValue != nil
}, set: { newValue in }, set: { newValue in
@@ -25,7 +25,7 @@ extension View {
} }
// periphery: ignore - not used yet but might be useful // periphery: ignore - not used yet but might be useful
func alert<Item, Actions>(item: Binding<Item?>, @ViewBuilder actions: (Item) -> Actions) -> some View where Item: AlertProtocol, Actions: View { func alert<Item: AlertProtocol, Actions: View>(item: Binding<Item?>, @ViewBuilder actions: (Item) -> Actions) -> some View {
let binding = Binding<Bool>(get: { let binding = Binding<Bool>(get: {
item.wrappedValue != nil item.wrappedValue != nil
}, set: { newValue in }, set: { newValue in

View File

@@ -9,7 +9,7 @@
import Foundation import Foundation
extension AttributedString { extension AttributedString {
// faster than doing `String(characters)`: https://forums.swift.org/t/attributedstring-to-string/61667 /// faster than doing `String(characters)`: https://forums.swift.org/t/attributedstring-to-string/61667
var string: String { var string: String {
String(characters[...]) String(characters[...])
} }

View File

@@ -13,9 +13,9 @@ protocol ConfirmationDialogProtocol {
} }
extension View { extension View {
func confirmationDialog<Item, Actions>(item: Binding<Item?>, func confirmationDialog<Item: ConfirmationDialogProtocol, Actions: View>(item: Binding<Item?>,
titleVisibility: Visibility = .automatic, titleVisibility: Visibility = .automatic,
@ViewBuilder actions: (Item) -> Actions) -> some View where Item: ConfirmationDialogProtocol, Actions: View { @ViewBuilder actions: (Item) -> Actions) -> some View {
let binding = Binding<Bool>(get: { let binding = Binding<Bool>(get: {
item.wrappedValue != nil item.wrappedValue != nil
}, set: { newValue in }, set: { newValue in
@@ -27,10 +27,10 @@ extension View {
} }
// periphery: ignore - not used yet but might be useful // periphery: ignore - not used yet but might be useful
func confirmationDialog<Item, Actions, Message>(item: Binding<Item?>, func confirmationDialog<Item: ConfirmationDialogProtocol, Actions: View, Message: View>(item: Binding<Item?>,
titleVisibility: Visibility = .automatic, titleVisibility: Visibility = .automatic,
@ViewBuilder actions: (Item) -> Actions, @ViewBuilder actions: (Item) -> Actions,
@ViewBuilder message: (Item) -> Message) -> some View where Item: ConfirmationDialogProtocol, Actions: View, Message: View { @ViewBuilder message: (Item) -> Message) -> some View {
let binding = Binding<Bool>(get: { let binding = Binding<Bool>(get: {
item.wrappedValue != nil item.wrappedValue != nil
}, set: { newValue in }, set: { newValue in

View File

@@ -67,8 +67,8 @@ extension Date {
} }
private extension DateFormatter { private extension DateFormatter {
// There doesn't appear to be a way to get "Today" out of /// There doesn't appear to be a way to get "Today" out of
// `Date.RelativeFormatStyle` so use the old way instead 😐 /// `Date.RelativeFormatStyle` so use the old way instead 😐
static let relative: DateFormatter = { static let relative: DateFormatter = {
let formatter = DateFormatter() let formatter = DateFormatter()
formatter.doesRelativeDateFormatting = true formatter.doesRelativeDateFormatting = true

View File

@@ -197,5 +197,7 @@ extension NSItemProvider {
} }
private extension NSString { private extension NSString {
var hasPathExtension: Bool { !pathExtension.isEmpty } var hasPathExtension: Bool {
!pathExtension.isEmpty
}
} }

View File

@@ -7,7 +7,6 @@
// //
import Foundation import Foundation
import OrderedCollections import OrderedCollections
extension OrderedSet { extension OrderedSet {

View File

@@ -9,7 +9,7 @@
import SwiftUI import SwiftUI
extension Section where Parent == Color, Content == EmptyView, Footer == EmptyView { extension Section where Parent == Color, Content == EmptyView, Footer == EmptyView {
// An empty section whose purpose is to keep Form's background color when there is no content into it. /// An empty section whose purpose is to keep Form's background color when there is no content into it.
static var empty: some View { static var empty: some View {
Section { Section {
EmptyView() EmptyView()

View File

@@ -135,12 +135,29 @@ extension URL {
// MARK: Mocks // MARK: Mocks
static var mockMXCAudio: URL { "mxc://matrix.org/1234567890AuDiO" } static var mockMXCAudio: URL {
static var mockMXCFile: URL { "mxc://matrix.org/1234567890FiLe" } "mxc://matrix.org/1234567890AuDiO"
static var mockMXCImage: URL { "mxc://matrix.org/1234567890ImAgE" } }
static var mockMXCVideo: URL { "mxc://matrix.org/1234567890ViDeO" }
static var mockMXCAvatar: URL { "mxc://matrix.org/1234567890AvAtAr" } static var mockMXCFile: URL {
static var mockMXCUserAvatar: URL { "mxc://matrix.org/1234567890AvAtArUsEr" } "mxc://matrix.org/1234567890FiLe"
}
static var mockMXCImage: URL {
"mxc://matrix.org/1234567890ImAgE"
}
static var mockMXCVideo: URL {
"mxc://matrix.org/1234567890ViDeO"
}
static var mockMXCAvatar: URL {
"mxc://matrix.org/1234567890AvAtAr"
}
static var mockMXCUserAvatar: URL {
"mxc://matrix.org/1234567890AvAtArUsEr"
}
} }
// MARK: - Helpers // MARK: - Helpers

View File

@@ -96,7 +96,7 @@ extension XCTestCase {
timeout: TimeInterval = 10, timeout: TimeInterval = 10,
message: String? = nil) -> DeferredFulfillment<P.Output> { message: String? = nil) -> DeferredFulfillment<P.Output> {
var expectedOrder = transitionValues var expectedOrder = transitionValues
let deferred = deferFulfillment(publisher, timeout: timeout, message: message) { value in return deferFulfillment(publisher, timeout: timeout, message: message) { value in
let receivedValue = value[keyPath: keyPath] let receivedValue = value[keyPath: keyPath]
if let index = expectedOrder.firstIndex(where: { $0 == receivedValue }), index == 0 { if let index = expectedOrder.firstIndex(where: { $0 == receivedValue }), index == 0 {
expectedOrder.remove(at: index) expectedOrder.remove(at: index)
@@ -104,8 +104,6 @@ extension XCTestCase {
return expectedOrder.isEmpty return expectedOrder.isEmpty
} }
return deferred
} }
/// XCTest utility that assists in subscribing to an async sequence and deferring the fulfilment and results until some other actions have been performed. /// XCTest utility that assists in subscribing to an async sequence and deferring the fulfilment and results until some other actions have been performed.
@@ -120,15 +118,13 @@ extension XCTestCase {
timeout: TimeInterval = 10, timeout: TimeInterval = 10,
message: String? = nil) -> DeferredFulfillment<Value> { message: String? = nil) -> DeferredFulfillment<Value> {
var expectedOrder = transitionValues var expectedOrder = transitionValues
let deferred = deferFulfillment(asyncSequence, timeout: timeout, message: message) { value in return deferFulfillment(asyncSequence, timeout: timeout, message: message) { value in
if let index = expectedOrder.firstIndex(where: { $0 == value }), index == 0 { if let index = expectedOrder.firstIndex(where: { $0 == value }), index == 0 {
expectedOrder.remove(at: index) expectedOrder.remove(at: index)
} }
return expectedOrder.isEmpty return expectedOrder.isEmpty
} }
return deferred
} }
/// XCTest utility that assists in subscribing to a publisher and deferring the failure for a particular value until some other actions have been performed. /// XCTest utility that assists in subscribing to a publisher and deferring the failure for a particular value until some other actions have been performed.

View File

@@ -72,13 +72,13 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
return result return result
} }
// Do not use the default HTML renderer of NSAttributedString because this method /// Do not use the default HTML renderer of NSAttributedString because this method
// runs on the UI thread which we want to avoid because renderHTMLString is called /// runs on the UI thread which we want to avoid because renderHTMLString is called
// most of the time from a background thread. /// most of the time from a background thread.
// Use DTCoreText HTML renderer instead. /// Use DTCoreText HTML renderer instead.
// Using DTCoreText, which renders static string, helps to avoid code injection attacks /// Using DTCoreText, which renders static string, helps to avoid code injection attacks
// that could happen with the default HTML renderer of NSAttributedString which is a /// that could happen with the default HTML renderer of NSAttributedString which is a
// webview. /// webview.
func fromHTML(_ htmlString: String?) -> AttributedString? { func fromHTML(_ htmlString: String?) -> AttributedString? {
guard let originalHTMLString = htmlString else { guard let originalHTMLString = htmlString else {
return nil return nil

View File

@@ -89,7 +89,9 @@ extension AttributeScopes {
let uiKit: UIKitAttributes let uiKit: UIKitAttributes
} }
var elementX: ElementXAttributes.Type { ElementXAttributes.self } var elementX: ElementXAttributes.Type {
ElementXAttributes.self
}
} }
// periphery: ignore - required to make NSAttributedString to AttributedString conversion even if not used directly // periphery: ignore - required to make NSAttributedString to AttributedString conversion even if not used directly

View File

@@ -26,7 +26,9 @@ enum Tracing {
/// This basically only affects ``logFiles``, and doesn't inform the SDK to write /// This basically only affects ``logFiles``, and doesn't inform the SDK to write
/// the logs to a different directory, which should be done before setting this. /// the logs to a different directory, which should be done before setting this.
static var logsDirectoryOverride: URL? static var logsDirectoryOverride: URL?
static var legacyLogsDirectory: URL { .appGroupContainerDirectory } static var legacyLogsDirectory: URL {
.appGroupContainerDirectory
}
static let fileExtension = "log" static let fileExtension = "log"
@@ -60,7 +62,9 @@ enum Tracing {
} }
/// A list of all log file URLs, sorted chronologically. /// A list of all log file URLs, sorted chronologically.
static var logFiles: [URL] { logFiles(in: logsDirectory) } static var logFiles: [URL] {
logFiles(in: logsDirectory)
}
/// Collect all of the logs in the given directory, sorting them chronologically. /// Collect all of the logs in the given directory, sorting them chronologically.
private static func logFiles(in directory: URL) -> [URL] { private static func logFiles(in directory: URL) -> [URL] {

View File

@@ -8,7 +8,7 @@
import Foundation import Foundation
/* /**
Behavior mode of the current user's location, can be hidden, only shown and shown following the user Behavior mode of the current user's location, can be hidden, only shown and shown following the user
*/ */
enum ShowUserLocationMode { enum ShowUserLocationMode {

View File

@@ -114,7 +114,9 @@ struct MapLibreStaticMapView_Previews: PreviewProvider, TestablePreview {
} }
private struct MapTilerURLBuilderMock: MapTilerURLBuilderProtocol { private struct MapTilerURLBuilderMock: MapTilerURLBuilderProtocol {
func interactiveMapURL(for style: MapTilerStyle) -> URL? { nil } func interactiveMapURL(for style: MapTilerStyle) -> URL? {
nil
}
func staticMapTileImageURL(for style: MapTilerStyle, func staticMapTileImageURL(for style: MapTilerStyle,
coordinates: CLLocationCoordinate2D, coordinates: CLLocationCoordinate2D,

View File

@@ -8,7 +8,7 @@
import Foundation import Foundation
// https://spec.matrix.org/latest/appendices/#identifier-grammar /// https://spec.matrix.org/latest/appendices/#identifier-grammar
enum MatrixEntityRegex: String { enum MatrixEntityRegex: String {
case homeserver case homeserver
case userID case userID

View File

@@ -22,7 +22,7 @@ final class MessageTextView: UITextView, PillAttachmentViewProviderDelegate, UIG
super.addGestureRecognizer(gestureRecognizer) super.addGestureRecognizer(gestureRecognizer)
} }
// This prevents the magnifying glass from showing up /// This prevents the magnifying glass from showing up
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if otherGestureRecognizer is UILongPressGestureRecognizer { if otherGestureRecognizer is UILongPressGestureRecognizer {
return false return false

View File

@@ -9,7 +9,6 @@
import SwiftUI import SwiftUI
import SwiftUIIntrospect import SwiftUIIntrospect
import UIKit import UIKit
import WysiwygComposer import WysiwygComposer
protocol PillAttachmentViewProviderDelegate: AnyObject { protocol PillAttachmentViewProviderDelegate: AnyObject {

View File

@@ -29,7 +29,6 @@ struct PillView: View {
} }
} }
@ViewBuilder
private var mainContent: some View { private var mainContent: some View {
Text(context.viewState.displayText) Text(context.viewState.displayText)
.font(.compound.bodyLGSemibold) .font(.compound.bodyLGSemibold)

View File

@@ -8,7 +8,7 @@
import Foundation import Foundation
// In the future we might use this to do some customisation in what is plain text used to represent mentions. /// In the future we might use this to do some customisation in what is plain text used to represent mentions.
struct PlainMentionBuilder: MentionBuilderProtocol { struct PlainMentionBuilder: MentionBuilderProtocol {
func handleEventOnRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, eventID: String, roomAlias: String) { } func handleEventOnRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, eventID: String, roomAlias: String) { }

View File

@@ -26,123 +26,177 @@ final class SDKListener<T> {
// MARK: QRCodeLoginService // MARK: QRCodeLoginService
extension SDKListener: QrLoginProgressListener where T == QrLoginProgress { extension SDKListener: QrLoginProgressListener where T == QrLoginProgress {
func onUpdate(state: QrLoginProgress) { onUpdateClosure(state) } func onUpdate(state: QrLoginProgress) {
onUpdateClosure(state)
}
} }
extension SDKListener: GrantQrLoginProgressListener where T == GrantQrLoginProgress { extension SDKListener: GrantQrLoginProgressListener where T == GrantQrLoginProgress {
func onUpdate(state: GrantQrLoginProgress) { onUpdateClosure(state) } func onUpdate(state: GrantQrLoginProgress) {
onUpdateClosure(state)
}
} }
extension SDKListener: GrantGeneratedQrLoginProgressListener where T == GrantGeneratedQrLoginProgress { extension SDKListener: GrantGeneratedQrLoginProgressListener where T == GrantGeneratedQrLoginProgress {
func onUpdate(state: GrantGeneratedQrLoginProgress) { onUpdateClosure(state) } func onUpdate(state: GrantGeneratedQrLoginProgress) {
onUpdateClosure(state)
}
} }
// MARK: ClientProxy // MARK: ClientProxy
extension SDKListener: MediaPreviewConfigListener where T == MediaPreviewConfig? { extension SDKListener: MediaPreviewConfigListener where T == MediaPreviewConfig? {
func onChange(mediaPreviewConfig: MediaPreviewConfig?) { onUpdateClosure(mediaPreviewConfig) } func onChange(mediaPreviewConfig: MediaPreviewConfig?) {
onUpdateClosure(mediaPreviewConfig)
}
} }
extension SDKListener: SyncServiceStateObserver where T == SyncServiceState { extension SDKListener: SyncServiceStateObserver where T == SyncServiceState {
func onUpdate(state: SyncServiceState) { onUpdateClosure(state) } func onUpdate(state: SyncServiceState) {
onUpdateClosure(state)
}
} }
extension SDKListener: RoomListServiceStateListener where T == RoomListServiceState { extension SDKListener: RoomListServiceStateListener where T == RoomListServiceState {
func onUpdate(state: RoomListServiceState) { onUpdateClosure(state) } func onUpdate(state: RoomListServiceState) {
onUpdateClosure(state)
}
} }
extension SDKListener: RoomListServiceSyncIndicatorListener where T == RoomListServiceSyncIndicator { extension SDKListener: RoomListServiceSyncIndicatorListener where T == RoomListServiceSyncIndicator {
func onUpdate(syncIndicator: RoomListServiceSyncIndicator) { onUpdateClosure(syncIndicator) } func onUpdate(syncIndicator: RoomListServiceSyncIndicator) {
onUpdateClosure(syncIndicator)
}
} }
extension SDKListener: VerificationStateListener where T == VerificationState { extension SDKListener: VerificationStateListener where T == VerificationState {
func onUpdate(status: VerificationState) { onUpdateClosure(status) } func onUpdate(status: VerificationState) {
onUpdateClosure(status)
}
} }
extension SDKListener: IgnoredUsersListener where T == [String] { extension SDKListener: IgnoredUsersListener where T == [String] {
func call(ignoredUserIds: [String]) { onUpdateClosure(ignoredUserIds) } func call(ignoredUserIds: [String]) {
onUpdateClosure(ignoredUserIds)
}
} }
extension SDKListener: SendQueueRoomErrorListener where T == (String, ClientError) { extension SDKListener: SendQueueRoomErrorListener where T == (String, ClientError) {
func onError(roomId: String, error: ClientError) { onUpdateClosure((roomId, error)) } func onError(roomId: String, error: ClientError) {
onUpdateClosure((roomId, error))
}
} }
// MARK: SecureBackupController // MARK: SecureBackupController
extension SDKListener: BackupStateListener where T == BackupState { extension SDKListener: BackupStateListener where T == BackupState {
func onUpdate(status: BackupState) { onUpdateClosure(status) } func onUpdate(status: BackupState) {
onUpdateClosure(status)
}
} }
extension SDKListener: RecoveryStateListener where T == RecoveryState { extension SDKListener: RecoveryStateListener where T == RecoveryState {
func onUpdate(status: RecoveryState) { onUpdateClosure(status) } func onUpdate(status: RecoveryState) {
onUpdateClosure(status)
}
} }
extension SDKListener: EnableRecoveryProgressListener where T == EnableRecoveryProgress { extension SDKListener: EnableRecoveryProgressListener where T == EnableRecoveryProgress {
func onUpdate(status: EnableRecoveryProgress) { onUpdateClosure(status) } func onUpdate(status: EnableRecoveryProgress) {
onUpdateClosure(status)
}
} }
extension SDKListener: BackupSteadyStateListener where T == BackupUploadState { extension SDKListener: BackupSteadyStateListener where T == BackupUploadState {
func onUpdate(status: BackupUploadState) { onUpdateClosure(status) } func onUpdate(status: BackupUploadState) {
onUpdateClosure(status)
}
} }
// MARK: RoomSummaryProvider // MARK: RoomSummaryProvider
extension SDKListener: RoomListEntriesListener where T == [RoomListEntriesUpdate] { extension SDKListener: RoomListEntriesListener where T == [RoomListEntriesUpdate] {
func onUpdate(roomEntriesUpdate: [RoomListEntriesUpdate]) { onUpdateClosure(roomEntriesUpdate) } func onUpdate(roomEntriesUpdate: [RoomListEntriesUpdate]) {
onUpdateClosure(roomEntriesUpdate)
}
} }
extension SDKListener: RoomListLoadingStateListener where T == RoomListLoadingState { extension SDKListener: RoomListLoadingStateListener where T == RoomListLoadingState {
func onUpdate(state: RoomListLoadingState) { onUpdateClosure(state) } func onUpdate(state: RoomListLoadingState) {
onUpdateClosure(state)
}
} }
// MARK: Spaces // MARK: Spaces
extension SDKListener: SpaceServiceJoinedSpacesListener where T == [SpaceListUpdate] { extension SDKListener: SpaceServiceJoinedSpacesListener where T == [SpaceListUpdate] {
func onUpdate(rooms: [SpaceListUpdate]) { onUpdateClosure(rooms) } func onUpdate(rooms: [SpaceListUpdate]) {
onUpdateClosure(rooms)
}
} }
extension SDKListener: SpaceRoomListEntriesListener where T == [SpaceListUpdate] { extension SDKListener: SpaceRoomListEntriesListener where T == [SpaceListUpdate] {
func onUpdate(roomUpdates: [SpaceListUpdate]) { onUpdateClosure(roomUpdates) } func onUpdate(roomUpdates: [SpaceListUpdate]) {
onUpdateClosure(roomUpdates)
}
} }
extension SDKListener: SpaceRoomListPaginationStateListener where T == SpaceRoomListPaginationState { extension SDKListener: SpaceRoomListPaginationStateListener where T == SpaceRoomListPaginationState {
func onUpdate(paginationState: SpaceRoomListPaginationState) { onUpdateClosure(paginationState) } func onUpdate(paginationState: SpaceRoomListPaginationState) {
onUpdateClosure(paginationState)
}
} }
extension SDKListener: SpaceRoomListSpaceListener where T == SpaceRoom? { extension SDKListener: SpaceRoomListSpaceListener where T == SpaceRoom? {
func onUpdate(space: SpaceRoom?) { onUpdateClosure(space) } func onUpdate(space: SpaceRoom?) {
onUpdateClosure(space)
}
} }
extension SDKListener: SpaceServiceSpaceFiltersListener where T == [SpaceFilterUpdate] { extension SDKListener: SpaceServiceSpaceFiltersListener where T == [SpaceFilterUpdate] {
func onUpdate(filterUpdates: [SpaceFilterUpdate]) { onUpdateClosure(filterUpdates) } func onUpdate(filterUpdates: [SpaceFilterUpdate]) {
onUpdateClosure(filterUpdates)
}
} }
// MARK: Room // MARK: Room
extension SDKListener: RoomInfoListener where T == RoomInfo { extension SDKListener: RoomInfoListener where T == RoomInfo {
func call(roomInfo: RoomInfo) { onUpdateClosure(roomInfo) } func call(roomInfo: RoomInfo) {
onUpdateClosure(roomInfo)
}
} }
extension SDKListener: CallDeclineListener where T == String { extension SDKListener: CallDeclineListener where T == String {
func call(declinerUserId: String) { onUpdateClosure(declinerUserId) } func call(declinerUserId: String) {
onUpdateClosure(declinerUserId)
}
} }
extension SDKListener: TypingNotificationsListener where T == [String] { extension SDKListener: TypingNotificationsListener where T == [String] {
func call(typingUserIds: [String]) { onUpdateClosure(typingUserIds) } func call(typingUserIds: [String]) {
onUpdateClosure(typingUserIds)
}
} }
extension SDKListener: IdentityStatusChangeListener where T == [IdentityStatusChange] { extension SDKListener: IdentityStatusChangeListener where T == [IdentityStatusChange] {
func call(identityStatusChange: [IdentityStatusChange]) { onUpdateClosure(identityStatusChange) } func call(identityStatusChange: [IdentityStatusChange]) {
onUpdateClosure(identityStatusChange)
}
} }
extension SDKListener: KnockRequestsListener where T == [KnockRequest] { extension SDKListener: KnockRequestsListener where T == [KnockRequest] {
func call(joinRequests: [KnockRequest]) { onUpdateClosure(joinRequests) } func call(joinRequests: [KnockRequest]) {
onUpdateClosure(joinRequests)
}
} }
// MARK: TimelineProxy // MARK: TimelineProxy
extension SDKListener: PaginationStatusListener where T == RoomPaginationStatus { extension SDKListener: PaginationStatusListener where T == RoomPaginationStatus {
func onUpdate(status: RoomPaginationStatus) { onUpdateClosure(status) } func onUpdate(status: RoomPaginationStatus) {
onUpdateClosure(status)
}
} }
extension SDKListener: ProgressWatcher where T == Double { extension SDKListener: ProgressWatcher where T == Double {
@@ -156,11 +210,15 @@ extension SDKListener: ProgressWatcher where T == Double {
// MARK: TimelineItemProvider // MARK: TimelineItemProvider
extension SDKListener: TimelineListener where T == [TimelineDiff] { extension SDKListener: TimelineListener where T == [TimelineDiff] {
func onUpdate(diff: [TimelineDiff]) { onUpdateClosure(diff) } func onUpdate(diff: [TimelineDiff]) {
onUpdateClosure(diff)
}
} }
// MARK: RoomDirectorySearchProxy // MARK: RoomDirectorySearchProxy
extension SDKListener: RoomDirectorySearchEntriesListener where T == [RoomDirectorySearchEntryUpdate] { extension SDKListener: RoomDirectorySearchEntriesListener where T == [RoomDirectorySearchEntryUpdate] {
func onUpdate(roomEntriesUpdate: [RoomDirectorySearchEntryUpdate]) { onUpdateClosure(roomEntriesUpdate) } func onUpdate(roomEntriesUpdate: [RoomDirectorySearchEntryUpdate]) {
onUpdateClosure(roomEntriesUpdate)
}
} }

View File

@@ -19,7 +19,7 @@ public extension Animation {
return animation.disabledIfReduceMotionEnabled() return animation.disabledIfReduceMotionEnabled()
} }
// `noAnimation` if running tests, otherwise `self` if `UIAccessibility.isReduceMotionEnabled` is false /// `noAnimation` if running tests, otherwise `self` if `UIAccessibility.isReduceMotionEnabled` is false
func disabledDuringTests() -> Self { func disabledDuringTests() -> Self {
let animation: Animation = ProcessInfo.isRunningTests ? .noAnimation : self let animation: Animation = ProcessInfo.isRunningTests ? .noAnimation : self
return animation.disabledIfReduceMotionEnabled() return animation.disabledIfReduceMotionEnabled()

View File

@@ -10,7 +10,9 @@ import SwiftUI
extension ButtonStyle where Self == MenuSheetButtonStyle { extension ButtonStyle where Self == MenuSheetButtonStyle {
/// A button style for buttons that are within a menu that is being presented as a sheet. /// A button style for buttons that are within a menu that is being presented as a sheet.
static var menuSheet: Self { MenuSheetButtonStyle() } static var menuSheet: Self {
MenuSheetButtonStyle()
}
} }
/// The style used for buttons that are part of a menu that's presented as /// The style used for buttons that are part of a menu that's presented as

View File

@@ -31,7 +31,6 @@ extension View {
} }
} }
@ViewBuilder
func mediaGalleryTimelineAspectRatio(imageInfo: ImageInfoProxy?) -> some View { func mediaGalleryTimelineAspectRatio(imageInfo: ImageInfoProxy?) -> some View {
aspectRatio(imageInfo?.aspectRatio, contentMode: .fill) aspectRatio(imageInfo?.aspectRatio, contentMode: .fill)
} }

View File

@@ -53,7 +53,6 @@ private struct SearchControllerModifier: ViewModifier {
/// is `false`, checking if this value is `false` is pretty much meaningless. /// is `false`, checking if this value is `false` is pretty much meaningless.
@State private var isSearching = false @State private var isSearching = false
@ViewBuilder
func body(content: Content) -> some View { func body(content: Content) -> some View {
let text: Text? = if let placeholder { let text: Text? = if let placeholder {
Text(placeholder) Text(placeholder)
@@ -191,7 +190,9 @@ private struct SearchController: UIViewControllerRepresentable {
} }
@available(*, unavailable) @available(*, unavailable)
required init?(coder: NSCoder) { fatalError() } required init?(coder: NSCoder) {
fatalError()
}
override func willMove(toParent parent: UIViewController?) { override func willMove(toParent parent: UIViewController?) {
parent?.navigationItem.searchController = searchController parent?.navigationItem.searchController = searchController

View File

@@ -135,9 +135,7 @@ private struct LoadableImageContent<TransformerView: View, PlaceholderView: View
ZStack { ZStack {
switch (contentLoader.content, shouldRender) { switch (contentLoader.content, shouldRender) {
case (.image(let image), true): case (.image(let image), true):
transformer( transformer(AnyView(Image(uiImage: image).resizable()))
AnyView(Image(uiImage: image).resizable())
)
case (.gifData, true): case (.gifData, true):
transformer(AnyView(KFAnimatedImage(source: .provider(self)))) transformer(AnyView(KFAnimatedImage(source: .provider(self))))
case (.none, _), (_, false): case (.none, _), (_, false):
@@ -170,7 +168,7 @@ private struct LoadableImageContent<TransformerView: View, PlaceholderView: View
} }
} }
// Note: Returns `AnyView` as this is what `transformer` expects. /// Note: Returns `AnyView` as this is what `transformer` expects.
var blurHashView: AnyView? { var blurHashView: AnyView? {
if let blurhash, if let blurhash,
// Build a small blurhash image so that it's fast // Build a small blurhash image so that it's fast
@@ -387,7 +385,10 @@ struct LoadableImage_Previews: PreviewProvider, TestablePreview {
} }
} }
static func placeholder() -> some View { Color.compound._bgBubbleIncoming } static func placeholder() -> some View {
Color.compound._bgBubbleIncoming
}
static func transformer(_ view: AnyView) -> some View { static func transformer(_ view: AnyView) -> some View {
view.overlay { view.overlay {
Image(systemSymbol: .playCircleFill) Image(systemSymbol: .playCircleFill)

View File

@@ -17,7 +17,9 @@ struct UserProfileListRow: View {
let kind: ListRow<LoadableAvatarImage, EmptyView, EmptyView, Bool>.Kind<EmptyView, Bool> let kind: ListRow<LoadableAvatarImage, EmptyView, EmptyView, Bool>.Kind<EmptyView, Bool>
var isUnknownProfile: Bool { !user.isVerified && membership == nil } var isUnknownProfile: Bool {
!user.isVerified && membership == nil
}
private var subtitle: String? { private var subtitle: String? {
guard !isUnknownProfile else { return L10n.commonInviteUnknownProfile } guard !isUnknownProfile else { return L10n.commonInviteUnknownProfile }

View File

@@ -58,12 +58,10 @@ private struct VisualListItemLabelStyle: LabelStyle {
struct VisualListItem_Previews: PreviewProvider, TestablePreview { struct VisualListItem_Previews: PreviewProvider, TestablePreview {
static let strings = AnalyticsPromptScreenStrings(termsURL: ServiceLocator.shared.settings.analyticsTermsURL) static let strings = AnalyticsPromptScreenStrings(termsURL: ServiceLocator.shared.settings.analyticsTermsURL)
@ViewBuilder
static var testImage1: some View { static var testImage1: some View {
Image(systemName: "circle") Image(systemName: "circle")
} }
@ViewBuilder
static var testImage2: some View { static var testImage2: some View {
Image(systemName: "square") Image(systemName: "square")
} }

View File

@@ -13,8 +13,13 @@ enum UserIndicatorType: Equatable {
case toast(progress: UserIndicator.Progress?) case toast(progress: UserIndicator.Progress?)
case modal(progress: UserIndicator.Progress?, interactiveDismissDisabled: Bool, allowsInteraction: Bool) case modal(progress: UserIndicator.Progress?, interactiveDismissDisabled: Bool, allowsInteraction: Bool)
static var toast: Self { .toast(progress: .none) } static var toast: Self {
static var modal: Self { .modal(progress: .indeterminate, interactiveDismissDisabled: false, allowsInteraction: false) } .toast(progress: .none)
}
static var modal: Self {
.modal(progress: .indeterminate, interactiveDismissDisabled: false, allowsInteraction: false)
}
} }
struct UserIndicator: Equatable, Identifiable { struct UserIndicator: Equatable, Identifiable {

View File

@@ -76,28 +76,24 @@ struct UserIndicatorModalView_Previews: PreviewProvider, TestablePreview {
VStack(spacing: 0) { VStack(spacing: 0) {
UserIndicatorModalView(indicator: UserIndicator(type: .modal, UserIndicatorModalView(indicator: UserIndicator(type: .modal,
title: "Successfully logged in", title: "Successfully logged in",
iconName: "checkmark") iconName: "checkmark"))
)
UserIndicatorModalView(indicator: UserIndicator(type: .modal(progress: .published(CurrentValueSubject<Double, Never>(0.5).asCurrentValuePublisher()), UserIndicatorModalView(indicator: UserIndicator(type: .modal(progress: .published(CurrentValueSubject<Double, Never>(0.5).asCurrentValuePublisher()),
interactiveDismissDisabled: false, interactiveDismissDisabled: false,
allowsInteraction: false), allowsInteraction: false),
title: "Successfully logged in", title: "Successfully logged in",
iconName: "checkmark") iconName: "checkmark"))
)
UserIndicatorModalView(indicator: UserIndicator(type: .modal(progress: .none, UserIndicatorModalView(indicator: UserIndicator(type: .modal(progress: .none,
interactiveDismissDisabled: false, interactiveDismissDisabled: false,
allowsInteraction: false), allowsInteraction: false),
title: "Successfully logged in", title: "Successfully logged in",
iconName: "checkmark") iconName: "checkmark"))
)
UserIndicatorModalView(indicator: UserIndicator(type: .modal, UserIndicatorModalView(indicator: UserIndicator(type: .modal,
title: "Successfully logged in", title: "Successfully logged in",
message: "You can now be happy.", message: "You can now be happy.",
iconName: "checkmark") iconName: "checkmark"))
)
} }
} }
} }

View File

@@ -17,7 +17,6 @@ struct UserIndicatorPresenter: View {
.animation(.elementDefault, value: userIndicatorController.activeIndicator) .animation(.elementDefault, value: userIndicatorController.activeIndicator)
} }
@ViewBuilder
private func indicatorViewFor(indicator: UserIndicator?) -> some View { private func indicatorViewFor(indicator: UserIndicator?) -> some View {
ZStack { // Need a container to properly animate transitions ZStack { // Need a container to properly animate transitions
if let indicator { if let indicator {

View File

@@ -83,10 +83,8 @@ private struct VoiceMessageButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View { func makeBody(configuration: Configuration) -> some View {
configuration.label configuration.label
.foregroundColor(isEnabled ? .compound.textSecondary.opacity(configuration.isPressed ? 0.6 : 1) : .compound.iconDisabled) .foregroundColor(isEnabled ? .compound.textSecondary.opacity(configuration.isPressed ? 0.6 : 1) : .compound.iconDisabled)
.background( .background(Circle()
Circle() .foregroundColor(configuration.isPressed ? .compound.bgSubtlePrimary : .compound.bgCanvasDefault))
.foregroundColor(configuration.isPressed ? .compound.bgSubtlePrimary : .compound.bgCanvasDefault)
)
} }
} }

View File

@@ -39,8 +39,7 @@ private struct WaveformInteractionModifier: ViewModifier {
isDragging = true isDragging = true
let progress = dragGesture.location.x / geometry.size.width let progress = dragGesture.location.x / geometry.size.width
onSeek(max(0, min(progress, 1.0))) onSeek(max(0, min(progress, 1.0)))
} })
)
.offset(x: -cursorInteractiveSize / 2, y: 0) .offset(x: -cursorInteractiveSize / 2, y: 0)
} }
.gesture(SpatialTapGesture() .gesture(SpatialTapGesture()

View File

@@ -27,9 +27,15 @@ struct AppLockScreenViewState: BindableState {
var bindings: AppLockScreenViewStateBindings var bindings: AppLockScreenViewStateBindings
/// The number of digits the user has entered so far. /// The number of digits the user has entered so far.
var numberOfDigitsEntered: Int { bindings.pinCode.count } var numberOfDigitsEntered: Int {
bindings.pinCode.count
}
/// Whether the subtitle is in a warning state or not. /// Whether the subtitle is in a warning state or not.
var isSubtitleWarning: Bool { numberOfPINAttempts > 0 } var isSubtitleWarning: Bool {
numberOfPINAttempts > 0
}
/// The string shown in the screen's subtitle. /// The string shown in the screen's subtitle.
var subtitle: String { var subtitle: String {
if !isSubtitleWarning { if !isSubtitleWarning {

View File

@@ -78,7 +78,7 @@ struct AppLockScreen: View {
/// The row of dots showing how many digits have been entered. /// The row of dots showing how many digits have been entered.
var pinInputField: some View { var pinInputField: some View {
HStack(spacing: 24) { HStack(spacing: 24) {
/// The size of each dot within the PIN input field. // The size of each dot within the PIN input field.
let pinDotSize: CGFloat = 14 let pinDotSize: CGFloat = 14
Circle() Circle()
.fill(context.viewState.numberOfDigitsEntered > 0 ? .compound.iconPrimary : .compound.bgSubtlePrimary) .fill(context.viewState.numberOfDigitsEntered > 0 ? .compound.iconPrimary : .compound.bgSubtlePrimary)

View File

@@ -117,7 +117,9 @@ struct AppLockScreenPINKeypad_Previews: PreviewProvider {
@StateObject var model = PreviewModel() @StateObject var model = PreviewModel()
class PreviewModel: ObservableObject { class PreviewModel: ObservableObject {
@Published var pinCode = "" @Published var pinCode = ""
var output: String { pinCode.isEmpty ? "Enter code" : pinCode } var output: String {
pinCode.isEmpty ? "Enter code" : pinCode
}
} }
var body: some View { var body: some View {

View File

@@ -17,9 +17,17 @@ struct AppLockSetupBiometricsScreenViewState: BindableState {
/// The supported biometry type on this device. /// The supported biometry type on this device.
let biometryType: LABiometryType let biometryType: LABiometryType
var icon: SFSymbol { biometryType.systemSymbol } var icon: SFSymbol {
var title: String { L10n.screenAppLockSetupBiometricUnlockAllowTitle(biometryType.localizedString) } biometryType.systemSymbol
var subtitle: String { L10n.screenAppLockSetupBiometricUnlockSubtitle(biometryType.localizedString) } }
var title: String {
L10n.screenAppLockSetupBiometricUnlockAllowTitle(biometryType.localizedString)
}
var subtitle: String {
L10n.screenAppLockSetupBiometricUnlockSubtitle(biometryType.localizedString)
}
} }
enum AppLockSetupBiometricsScreenViewAction { enum AppLockSetupBiometricsScreenViewAction {

View File

@@ -50,7 +50,10 @@ struct AppLockSetupPINScreenViewState: BindableState {
} }
/// Whether the subtitle is in a warning state or not. /// Whether the subtitle is in a warning state or not.
var isSubtitleWarning: Bool { mode == .unlock && numberOfUnlockAttempts > 0 } var isSubtitleWarning: Bool {
mode == .unlock && numberOfUnlockAttempts > 0
}
var subtitle: String { var subtitle: String {
guard mode == .unlock else { return L10n.screenAppLockSetupPinContext(InfoPlistReader.main.bundleDisplayName) } guard mode == .unlock else { return L10n.screenAppLockSetupPinContext(InfoPlistReader.main.bundleDisplayName) }
if !isSubtitleWarning { if !isSubtitleWarning {

View File

@@ -22,8 +22,13 @@ struct AppLockSetupSettingsScreenViewState: BindableState {
let biometryType: LABiometryType let biometryType: LABiometryType
var bindings: AppLockSetupSettingsScreenViewStateBindings var bindings: AppLockSetupSettingsScreenViewStateBindings
var supportsBiometrics: Bool { biometryType != .none } var supportsBiometrics: Bool {
var enableBiometricsTitle: String { L10n.screenAppLockSetupBiometricUnlockAllowTitle(biometryType.localizedString) } biometryType != .none
}
var enableBiometricsTitle: String {
L10n.screenAppLockSetupBiometricUnlockAllowTitle(biometryType.localizedString)
}
} }
struct AppLockSetupSettingsScreenViewStateBindings { struct AppLockSetupSettingsScreenViewStateBindings {

View File

@@ -26,12 +26,14 @@ enum LoginScreenCoordinatorAction {
case signedIn(UserSessionProtocol) case signedIn(UserSessionProtocol)
} }
// Note: This code was brought over from Riot, we should move the authentication service logic into the view model. /// Note: This code was brought over from Riot, we should move the authentication service logic into the view model.
final class LoginScreenCoordinator: CoordinatorProtocol { final class LoginScreenCoordinator: CoordinatorProtocol {
private let parameters: LoginScreenCoordinatorParameters private let parameters: LoginScreenCoordinatorParameters
private var viewModel: LoginScreenViewModelProtocol private var viewModel: LoginScreenViewModelProtocol
private var authenticationService: AuthenticationServiceProtocol { parameters.authenticationService } private var authenticationService: AuthenticationServiceProtocol {
parameters.authenticationService
}
private let actionsSubject: PassthroughSubject<LoginScreenCoordinatorAction, Never> = .init() private let actionsSubject: PassthroughSubject<LoginScreenCoordinatorAction, Never> = .init()
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()

View File

@@ -31,7 +31,9 @@ struct LoginScreenViewState: BindableState {
var bindings = LoginScreenBindings() var bindings = LoginScreenBindings()
/// The types of login supported by the homeserver. /// The types of login supported by the homeserver.
var loginMode: LoginMode { homeserver.loginMode } var loginMode: LoginMode {
homeserver.loginMode
}
/// `true` if the username and password are ready to be submitted. /// `true` if the username and password are ready to be submitted.
var hasValidCredentials: Bool { var hasValidCredentials: Bool {

View File

@@ -103,7 +103,9 @@ class OIDCAuthenticationPresenter: NSObject {
// MARK: ASWebAuthenticationPresentationContextProviding // MARK: ASWebAuthenticationPresentationContextProviding
extension OIDCAuthenticationPresenter: ASWebAuthenticationPresentationContextProviding { extension OIDCAuthenticationPresenter: ASWebAuthenticationPresentationContextProviding {
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { presentationAnchor } func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
presentationAnchor
}
} }
extension ASWebAuthenticationSession.Callback { extension ASWebAuthenticationSession.Callback {

View File

@@ -103,9 +103,9 @@ struct ServerConfirmationScreen: View {
} }
} }
// This is such a hack. I hate it! /// This is such a hack. I hate it!
// But We're not in a List/Form, the compound picker doesn't /// But We're not in a List/Form, the compound picker doesn't
// support icons and this screen's design might change so 🤷. /// support icons and this screen's design might change so 🤷.
private struct FakeInlinePicker: View { private struct FakeInlinePicker: View {
let items: [String] let items: [String]
let icon: KeyPath<CompoundIcons, Image> let icon: KeyPath<CompoundIcons, Image>

View File

@@ -22,12 +22,14 @@ enum ServerSelectionScreenCoordinatorAction {
case dismiss case dismiss
} }
// Note: This code was brought over from Riot, we should move the authentication service logic into the view model. /// Note: This code was brought over from Riot, we should move the authentication service logic into the view model.
final class ServerSelectionScreenCoordinator: CoordinatorProtocol { final class ServerSelectionScreenCoordinator: CoordinatorProtocol {
private let parameters: ServerSelectionScreenCoordinatorParameters private let parameters: ServerSelectionScreenCoordinatorParameters
private let userIndicatorController: UserIndicatorControllerProtocol private let userIndicatorController: UserIndicatorControllerProtocol
private var viewModel: ServerSelectionScreenViewModelProtocol private var viewModel: ServerSelectionScreenViewModelProtocol
private var authenticationService: AuthenticationServiceProtocol { parameters.authenticationService } private var authenticationService: AuthenticationServiceProtocol {
parameters.authenticationService
}
private let actionsSubject: PassthroughSubject<ServerSelectionScreenCoordinatorAction, Never> = .init() private let actionsSubject: PassthroughSubject<ServerSelectionScreenCoordinatorAction, Never> = .init()
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()

View File

@@ -34,14 +34,17 @@ enum SoftLogoutScreenCoordinatorResult: CustomStringConvertible {
} }
} }
// Note: This code was brought over from Riot, we should move the authentication service logic into the view model. /// Note: This code was brought over from Riot, we should move the authentication service logic into the view model.
final class SoftLogoutScreenCoordinator: CoordinatorProtocol { final class SoftLogoutScreenCoordinator: CoordinatorProtocol {
private let parameters: SoftLogoutScreenCoordinatorParameters private let parameters: SoftLogoutScreenCoordinatorParameters
private var viewModel: SoftLogoutScreenViewModelProtocol private var viewModel: SoftLogoutScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<SoftLogoutScreenCoordinatorResult, Never> = .init() private let actionsSubject: PassthroughSubject<SoftLogoutScreenCoordinatorResult, Never> = .init()
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
private var authenticationService: AuthenticationServiceProtocol { parameters.authenticationService } private var authenticationService: AuthenticationServiceProtocol {
parameters.authenticationService
}
private var oidcPresenter: OIDCAuthenticationPresenter? private var oidcPresenter: OIDCAuthenticationPresenter?
var actions: AnyPublisher<SoftLogoutScreenCoordinatorResult, Never> { var actions: AnyPublisher<SoftLogoutScreenCoordinatorResult, Never> {

View File

@@ -54,7 +54,9 @@ struct SoftLogoutScreenViewState: BindableState {
var bindings: SoftLogoutScreenBindings var bindings: SoftLogoutScreenBindings
/// The types of login supported by the homeserver. /// The types of login supported by the homeserver.
var loginMode: LoginMode { homeserver.loginMode } var loginMode: LoginMode {
homeserver.loginMode
}
/// The presentation anchor used for OIDC authentication. /// The presentation anchor used for OIDC authentication.
var window: UIWindow? var window: UIWindow?

View File

@@ -20,7 +20,9 @@ struct AuthenticationStartLogo: View {
/// The shape that the logo is composed on top of. /// The shape that the logo is composed on top of.
private let outerShape = RoundedRectangle(cornerRadius: 44) private let outerShape = RoundedRectangle(cornerRadius: 44)
private let outerShapeShadowColor = Color(red: 0.11, green: 0.11, blue: 0.13) private let outerShapeShadowColor = Color(red: 0.11, green: 0.11, blue: 0.13)
private var isLight: Bool { colorScheme == .light } private var isLight: Bool {
colorScheme == .light
}
var body: some View { var body: some View {
if hideBrandChrome { if hideBrandChrome {

View File

@@ -83,12 +83,10 @@ final class BugReportScreenCoordinator: CoordinatorProtocol {
private static let loadingIndicatorIdentifier = "\(BugReportScreenCoordinator.self)-Loading" private static let loadingIndicatorIdentifier = "\(BugReportScreenCoordinator.self)-Loading"
private func startLoading(label: String = L10n.commonLoading, progressPublisher: CurrentValuePublisher<Double, Never>) { private func startLoading(label: String = L10n.commonLoading, progressPublisher: CurrentValuePublisher<Double, Never>) {
parameters.userIndicatorController?.submitIndicator( parameters.userIndicatorController?.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
UserIndicator(id: Self.loadingIndicatorIdentifier, type: .modal(progress: .published(progressPublisher), interactiveDismissDisabled: false, allowsInteraction: true),
type: .modal(progress: .published(progressPublisher), interactiveDismissDisabled: false, allowsInteraction: true), title: label,
title: label, persistent: true))
persistent: true)
)
} }
private func stopLoading() { private func stopLoading() {

View File

@@ -15,8 +15,13 @@ struct BugReportScreen: View {
@Bindable var context: BugReportScreenViewModel.Context @Bindable var context: BugReportScreenViewModel.Context
var canSendLogFiles: Bool { context.viewState.canSendLogFiles } var canSendLogFiles: Bool {
var photosPickerTitle: String { context.viewState.screenshot == nil ? L10n.screenBugReportAttachScreenshot : L10n.screenBugReportEditScreenshot } context.viewState.canSendLogFiles
}
var photosPickerTitle: String {
context.viewState.screenshot == nil ? L10n.screenBugReportAttachScreenshot : L10n.screenBugReportEditScreenshot
}
var body: some View { var body: some View {
Form { Form {
@@ -89,7 +94,6 @@ struct BugReportScreen: View {
} }
} }
@ViewBuilder
private var attachScreenshotSection: some View { private var attachScreenshotSection: some View {
Section { Section {
ListRow(kind: .custom { ListRow(kind: .custom {

View File

@@ -225,7 +225,7 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
} }
} }
// This should always match the web app value /// This should always match the web app value
private static let earpieceID = "earpiece-id" private static let earpieceID = "earpiece-id"
private func handleOutputDeviceSelected(deviceID: String) { private func handleOutputDeviceSelected(deviceID: String) {

View File

@@ -197,8 +197,8 @@ private struct CallView: UIViewRepresentable {
} }
} }
// This function is called by the webview output routing button /// This function is called by the webview output routing button
// it allows to open the OS output selector using the hidden button. /// it allows to open the OS output selector using the hidden button.
private func tapRoutePickerView() { private func tapRoutePickerView() {
guard let button = routePickerView.subviews.first(where: { $0 is UIButton }) as? UIButton else { guard let button = routePickerView.subviews.first(where: { $0 is UIButton }) as? UIButton else {
return return

View File

@@ -45,7 +45,6 @@ struct EncryptionResetPasswordScreen: View {
.onAppear { textFieldFocus = true } .onAppear { textFieldFocus = true }
} }
@ViewBuilder
private var passwordSection: some View { private var passwordSection: some View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Text(L10n.commonPassword) Text(L10n.commonPassword)

View File

@@ -70,7 +70,6 @@ struct EncryptionResetScreen: View {
.environment(\.backgroundStyle, AnyShapeStyle(.compound.bgSubtleSecondary)) .environment(\.backgroundStyle, AnyShapeStyle(.compound.bgSubtleSecondary))
} }
@ViewBuilder
private func checkMarkItem(title: String, position: ListPosition, positive: Bool) -> some View { private func checkMarkItem(title: String, position: ListPosition, positive: Bool) -> some View {
VisualListItem(title: title, position: position) { VisualListItem(title: title, position: position) {
CompoundIcon(positive ? \.check : \.info) CompoundIcon(positive ? \.check : \.info)

View File

@@ -106,10 +106,10 @@ private struct MediaPreviewViewController: UIViewControllerRepresentable {
]) ])
} }
// Don't use viewWillAppear due to the following warning: /// Don't use viewWillAppear due to the following warning:
// Presenting view controller <QLPreviewController> from detached view controller <HostingController> is not supported, /// Presenting view controller <QLPreviewController> from detached view controller <HostingController> is not supported,
// and may result in incorrect safe area insets and a corrupt root presentation. Make sure <HostingController> is in /// and may result in incorrect safe area insets and a corrupt root presentation. Make sure <HostingController> is in
// the view controller hierarchy before presenting from it. Will become a hard exception in a future release. /// the view controller hierarchy before presenting from it. Will become a hard exception in a future release.
override func viewDidAppear(_ animated: Bool) { override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) super.viewDidAppear(animated)
@@ -153,7 +153,10 @@ private struct MediaPreviewViewController: UIViewControllerRepresentable {
class MediaPreviewItem: NSObject, QLPreviewItem { class MediaPreviewItem: NSObject, QLPreviewItem {
let file: MediaFileHandleProxy let file: MediaFileHandleProxy
var previewItemURL: URL? { file.url } var previewItemURL: URL? {
file.url
}
let previewItemTitle: String? let previewItemTitle: String?
init(file: MediaFileHandleProxy, title: String?) { init(file: MediaFileHandleProxy, title: String?) {

View File

@@ -114,8 +114,13 @@ class TimelineMediaPreviewDataSource: NSObject, QLPreviewControllerDataSource {
// MARK: - QLPreviewControllerDataSource // MARK: - QLPreviewControllerDataSource
var firstPreviewItemIndex: Int { backwardPadding } var firstPreviewItemIndex: Int {
var lastPreviewItemIndex: Int { backwardPadding + previewItems.count - 1 } backwardPadding
}
var lastPreviewItemIndex: Int {
backwardPadding + previewItems.count - 1
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int { func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
previewItems.count + backwardPadding + forwardPadding previewItems.count + backwardPadding + forwardPadding

View File

@@ -56,7 +56,10 @@ struct TimelineMediaPreviewViewState: BindableState {
var dataSource: TimelineMediaPreviewDataSource var dataSource: TimelineMediaPreviewDataSource
/// The media item that is currently being previewed. /// The media item that is currently being previewed.
var currentItem: TimelineMediaPreviewItem { dataSource.currentItem } var currentItem: TimelineMediaPreviewItem {
dataSource.currentItem
}
/// All of the available actions for the current item. /// All of the available actions for the current item.
var currentItemActions: TimelineItemMenuActions? var currentItemActions: TimelineItemMenuActions?

View File

@@ -101,10 +101,10 @@ private struct MediaPreviewViewController: UIViewControllerRepresentable {
]) ])
} }
// Don't use viewWillAppear due to the following warning: /// Don't use viewWillAppear due to the following warning:
// Presenting view controller <QLPreviewController> from detached view controller <HostingController> is not supported, /// Presenting view controller <QLPreviewController> from detached view controller <HostingController> is not supported,
// and may result in incorrect safe area insets and a corrupt root presentation. Make sure <HostingController> is in /// and may result in incorrect safe area insets and a corrupt root presentation. Make sure <HostingController> is in
// the view controller hierarchy before presenting from it. Will become a hard exception in a future release. /// the view controller hierarchy before presenting from it. Will become a hard exception in a future release.
override func viewDidAppear(_ animated: Bool) { override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) super.viewDidAppear(animated)

View File

@@ -236,5 +236,7 @@ class TimelineMediaPreviewViewModel: TimelineMediaPreviewViewModelType {
title: L10n.screenMediaDetailsNoMoreMediaToShow)) title: L10n.screenMediaDetailsNoMoreMediaToShow))
} }
private var statusIndicatorID: String { "\(Self.self)-Status" } private var statusIndicatorID: String {
"\(Self.self)-Status"
}
} }

View File

@@ -24,7 +24,10 @@ class TimelineMediaPreviewController: QLPreviewController {
private var cancellables: Set<AnyCancellable> = [] private var cancellables: Set<AnyCancellable> = []
private var navigationBar: UINavigationBar? { view.subviews.first?.subviews.first { $0 is UINavigationBar } as? UINavigationBar } private var navigationBar: UINavigationBar? {
view.subviews.first?.subviews.first { $0 is UINavigationBar } as? UINavigationBar
}
private var bottomBarItemsContainer: UIView? { private var bottomBarItemsContainer: UIView? {
if #available(iOS 26, *) { if #available(iOS 26, *) {
view.subviews.first?.subviews.last?.subviews.first view.subviews.first?.subviews.last?.subviews.first
@@ -33,8 +36,13 @@ class TimelineMediaPreviewController: QLPreviewController {
} }
} }
private var pageScrollView: UIScrollView? { view.firstScrollView() } private var pageScrollView: UIScrollView? {
private var captionView: UIView { captionHostingController.view } view.firstScrollView()
}
private var captionView: UIView {
captionHostingController.view
}
override var overrideUserInterfaceStyle: UIUserInterfaceStyle { override var overrideUserInterfaceStyle: UIUserInterfaceStyle {
get { .dark } get { .dark }
@@ -255,7 +263,9 @@ class TimelineMediaPreviewController: QLPreviewController {
private struct HeaderView: View { private struct HeaderView: View {
@ObservedObject var context: TimelineMediaPreviewViewModel.Context @ObservedObject var context: TimelineMediaPreviewViewModel.Context
private var currentItem: TimelineMediaPreviewItem { context.viewState.currentItem } private var currentItem: TimelineMediaPreviewItem {
context.viewState.currentItem
}
var body: some View { var body: some View {
switch currentItem { switch currentItem {
@@ -281,7 +291,9 @@ private struct HeaderView: View {
private struct DetailsButton: View { private struct DetailsButton: View {
@ObservedObject var context: TimelineMediaPreviewViewModel.Context @ObservedObject var context: TimelineMediaPreviewViewModel.Context
private var currentItem: TimelineMediaPreviewItem { context.viewState.currentItem } private var currentItem: TimelineMediaPreviewItem {
context.viewState.currentItem
}
var isHidden: Bool { var isHidden: Bool {
switch currentItem { switch currentItem {
@@ -301,7 +313,9 @@ private struct DetailsButton: View {
private struct CaptionView: View { private struct CaptionView: View {
@ObservedObject var context: TimelineMediaPreviewViewModel.Context @ObservedObject var context: TimelineMediaPreviewViewModel.Context
private var currentItem: TimelineMediaPreviewItem { context.viewState.currentItem } private var currentItem: TimelineMediaPreviewItem {
context.viewState.currentItem
}
var body: some View { var body: some View {
if case let .media(mediaItem) = currentItem, let caption = mediaItem.caption { if case let .media(mediaItem) = currentItem, let caption = mediaItem.caption {
@@ -323,7 +337,9 @@ private struct CaptionView: View {
private struct DownloadIndicatorView: View { private struct DownloadIndicatorView: View {
@ObservedObject var context: TimelineMediaPreviewViewModel.Context @ObservedObject var context: TimelineMediaPreviewViewModel.Context
private var currentItem: TimelineMediaPreviewItem { context.viewState.currentItem } private var currentItem: TimelineMediaPreviewItem {
context.viewState.currentItem
}
private var shouldShowDownloadIndicator: Bool { private var shouldShowDownloadIndicator: Bool {
switch currentItem { switch currentItem {

View File

@@ -11,7 +11,9 @@ import SwiftUI
struct TimelineMediaPreviewFileExportPicker: UIViewControllerRepresentable { struct TimelineMediaPreviewFileExportPicker: UIViewControllerRepresentable {
struct File: Identifiable { struct File: Identifiable {
let url: URL let url: URL
var id: String { url.absoluteString } var id: String {
url.absoluteString
}
} }
let file: File let file: File

View File

@@ -57,7 +57,6 @@ struct TimelineMediaPreviewRedactConfirmationView: View {
.padding(.horizontal, 24) .padding(.horizontal, 24)
} }
@ViewBuilder
private var preview: some View { private var preview: some View {
HStack(spacing: 12) { HStack(spacing: 12) {
if let mediaSource = item.thumbnailMediaSource { if let mediaSource = item.thumbnailMediaSource {

View File

@@ -130,7 +130,7 @@ struct HomeScreenViewState: BindableState {
} }
} }
// Used to hide all the rooms when the search field is focused and the query is empty /// Used to hide all the rooms when the search field is focused and the query is empty
var shouldHideRoomList: Bool { var shouldHideRoomList: Bool {
bindings.isSearchFieldFocused && bindings.searchQuery.isEmpty bindings.isSearchFieldFocused && bindings.searchQuery.isEmpty
} }

View File

@@ -29,7 +29,9 @@ private struct BloomModifier: ViewModifier {
@State private var height = CGFloat.zero @State private var height = CGFloat.zero
private var endPointY: CGFloat { hasSearchBar ? 0.35 : 0.55 } private var endPointY: CGFloat {
hasSearchBar ? 0.35 : 0.55
}
func body(content: Content) -> some View { func body(content: Content) -> some View {
content content
@@ -102,7 +104,9 @@ private struct OldBloomModifier: ViewModifier {
return bloom return bloom
} }
private var endPointY: CGFloat { hasSearchBar ? 0.5 : 0.7 } private var endPointY: CGFloat {
hasSearchBar ? 0.5 : 0.7
}
private var bloomGradient: some View { private var bloomGradient: some View {
LinearGradient(gradient: .compound.subtle, LinearGradient(gradient: .compound.subtle,
@@ -117,9 +121,9 @@ private struct OldBloomModifier: ViewModifier {
bloom.colorScheme == colorScheme && bloom.baseColor == .compound.gradientSubtleStop1 bloom.colorScheme == colorScheme && bloom.baseColor == .compound.gradientSubtleStop1
} }
// This is a class to avoid a "Modifying state during view update" warning when storing /// This is a class to avoid a "Modifying state during view update" warning when storing
// the result on the same run-loop - we want to avoid dispatching that to the next loop as /// the result on the same run-loop - we want to avoid dispatching that to the next loop as
// that can result in further (unnecessary) renders being made. /// that can result in further (unnecessary) renders being made.
class Bloom { class Bloom {
var image: UIImage? var image: UIImage?
var colorScheme: ColorScheme? var colorScheme: ColorScheme?

View File

@@ -8,7 +8,6 @@
import Combine import Combine
import Foundation import Foundation
import MatrixRustSDK import MatrixRustSDK
import OrderedCollections import OrderedCollections

View File

@@ -15,7 +15,9 @@ struct RoomListFiltersView: View {
/// When you connect a mouse on macOS the scrollbars aren't hidden. This is some extra padding /// When you connect a mouse on macOS the scrollbars aren't hidden. This is some extra padding
/// applied to the scroll view content to make sure the bars don't overlap the filters. /// applied to the scroll view content to make sure the bars don't overlap the filters.
private var macScrollBarPadding: CGFloat { ProcessInfo.processInfo.isiOSAppOnMac ? 16 : 0 } private var macScrollBarPadding: CGFloat {
ProcessInfo.processInfo.isiOSAppOnMac ? 16 : 0
}
var body: some View { var body: some View {
ScrollViewReader { proxy in ScrollViewReader { proxy in

View File

@@ -92,7 +92,6 @@ struct HomeScreenInviteCell: View {
} }
} }
@ViewBuilder
private var textualContent: some View { private var textualContent: some View {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
Text(title) Text(title)

View File

@@ -65,7 +65,6 @@ struct HomeScreenKnockedCell: View {
} }
} }
@ViewBuilder
private var textualContent: some View { private var textualContent: some View {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
Text(title) Text(title)

View File

@@ -76,7 +76,6 @@ struct HomeScreenRoomCell: View {
} }
} }
@ViewBuilder
private var header: some View { private var header: some View {
HStack(alignment: .top, spacing: 16) { HStack(alignment: .top, spacing: 16) {
Text(room.name) Text(room.name)
@@ -93,7 +92,6 @@ struct HomeScreenRoomCell: View {
} }
} }
@ViewBuilder
private var footer: some View { private var footer: some View {
HStack(alignment: .firstTextBaseline, spacing: 0) { HStack(alignment: .firstTextBaseline, spacing: 0) {
ZStack(alignment: .topLeading) { ZStack(alignment: .topLeading) {

View File

@@ -20,7 +20,6 @@ struct HomeScreenRoomList: View {
} }
} }
@ViewBuilder
private var content: some View { private var content: some View {
ForEach(context.viewState.visibleRooms) { room in ForEach(context.viewState.visibleRooms) { room in
switch room.type { switch room.type {

View File

@@ -56,7 +56,6 @@ struct JoinRoomScreen: View {
} }
} }
@ViewBuilder
private var defaultView: some View { private var defaultView: some View {
VStack(spacing: 16) { VStack(spacing: 16) {
RoomAvatarImage(avatar: context.viewState.avatar ?? .room(id: "", name: nil, avatarURL: nil), RoomAvatarImage(avatar: context.viewState.avatar ?? .room(id: "", name: nil, avatarURL: nil),
@@ -140,7 +139,6 @@ struct JoinRoomScreen: View {
} }
} }
@ViewBuilder
private var knockedView: some View { private var knockedView: some View {
VStack(spacing: 16) { VStack(spacing: 16) {
BigIcon(icon: \.checkCircleSolid, style: .successSolid) BigIcon(icon: \.checkCircleSolid, style: .successSolid)
@@ -157,7 +155,6 @@ struct JoinRoomScreen: View {
} }
} }
@ViewBuilder
private var knockMessage: some View { private var knockMessage: some View {
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
HStack(spacing: 0) { HStack(spacing: 0) {

View File

@@ -36,8 +36,8 @@ struct KnockRequestsListScreenViewState: BindableState {
var isKnockableRoom = true var isKnockableRoom = true
var handledEventIDs: Set<String> = [] var handledEventIDs: Set<String> = []
// If all the permissions are denied or the join rule changes while we are in the view /// If all the permissions are denied or the join rule changes while we are in the view
// we want to stop displaying any request /// we want to stop displaying any request
var shouldDisplayRequests: Bool { var shouldDisplayRequests: Bool {
!displayedRequests.isEmpty && isKnockableRoom && (canAccept || canDecline || canBan) !displayedRequests.isEmpty && isKnockableRoom && (canAccept || canDecline || canBan)
} }

View File

@@ -80,7 +80,6 @@ struct KnockRequestCell: View {
} }
} }
@ViewBuilder
private var actions: some View { private var actions: some View {
VStack(spacing: 16) { VStack(spacing: 16) {
if onDecline != nil || onAccept != nil { if onDecline != nil || onAccept != nil {
@@ -174,7 +173,9 @@ private struct DisclosableText: View {
} }
extension KnockRequestCellInfo: Identifiable { extension KnockRequestCellInfo: Identifiable {
var id: String { eventID } var id: String {
eventID
}
} }
struct KnockRequestCell_Previews: PreviewProvider, TestablePreview { struct KnockRequestCell_Previews: PreviewProvider, TestablePreview {

Some files were not shown because too many files have changed in this diff Show More