Update files following swiftformat upgrade
This commit is contained in:
committed by
Stefan Ceriu
parent
2bb26efbe1
commit
04053ae69b
@@ -11,7 +11,7 @@ SwiftLint.lint(.modifiedAndCreatedFiles(directory: nil),
|
||||
|
||||
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
|
||||
|
||||
// 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.")
|
||||
}
|
||||
|
||||
// Check for screenshots on view changes
|
||||
/// Check for screenshots on view changes
|
||||
let hasChangedViews = !editedFiles.filter { $0.lowercased().contains("/view") }.isEmpty
|
||||
if hasChangedViews {
|
||||
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
|
||||
if hasPngs {
|
||||
warn("You seem to have made changes to some resource images. Please consider using an SVG or PDF.")
|
||||
|
||||
@@ -118,7 +118,9 @@ struct PreviewsWrapperView: View {
|
||||
private let name: String
|
||||
private let previews: [_Preview]
|
||||
private(set) var currentIndex = -1
|
||||
var currentPreview: _Preview { previews[currentIndex] }
|
||||
var currentPreview: _Preview {
|
||||
previews[currentIndex]
|
||||
}
|
||||
|
||||
private(set) var isDone = false
|
||||
|
||||
|
||||
@@ -13,5 +13,7 @@ protocol AppSettingsHookProtocol {
|
||||
}
|
||||
|
||||
struct DefaultAppSettingsHook: AppSettingsHookProtocol {
|
||||
func configure(_ appSettings: AppSettings) -> AppSettings { appSettings }
|
||||
func configure(_ appSettings: AppSettings) -> AppSettings {
|
||||
appSettings
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,5 +13,7 @@ protocol BugReportHookProtocol {
|
||||
}
|
||||
|
||||
struct DefaultBugReportHook: BugReportHookProtocol {
|
||||
func update(_ bugReport: BugReport) -> BugReport { bugReport }
|
||||
func update(_ bugReport: BugReport) -> BugReport {
|
||||
bugReport
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,5 +13,7 @@ protocol ClientBuilderHookProtocol {
|
||||
}
|
||||
|
||||
struct DefaultClientBuilderHook: ClientBuilderHookProtocol {
|
||||
func configure(_ builder: ClientBuilder) -> ClientBuilder { builder }
|
||||
func configure(_ builder: ClientBuilder) -> ClientBuilder {
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,5 +12,7 @@ protocol DeveloperOptionsScreenHookProtocol {
|
||||
}
|
||||
|
||||
struct DefaultDeveloperOptionsScreenHook: DeveloperOptionsScreenHookProtocol {
|
||||
func generalSectionRows() -> AnyView? { nil }
|
||||
func generalSectionRows() -> AnyView? {
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,5 +15,7 @@ protocol RoomScreenHookProtocol {
|
||||
|
||||
struct DefaultRoomScreenHook: RoomScreenHookProtocol {
|
||||
func configure(with userSession: UserSessionProtocol?) async { }
|
||||
func update(_ viewState: RoomScreenViewState) -> RoomScreenViewState { viewState }
|
||||
func update(_ viewState: RoomScreenViewState) -> RoomScreenViewState {
|
||||
viewState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,18 +198,16 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(
|
||||
navigationRootCoordinator.toPresentable()
|
||||
.environment(\.analyticsService, ServiceLocator.shared.analytics)
|
||||
.onReceive(appSettings.$appAppearance) { [weak self] appAppearance in
|
||||
guard let self else { return }
|
||||
AnyView(navigationRootCoordinator.toPresentable()
|
||||
.environment(\.analyticsService, ServiceLocator.shared.analytics)
|
||||
.onReceive(appSettings.$appAppearance) { [weak self] appAppearance in
|
||||
guard let self else { return }
|
||||
|
||||
windowManager.windows.forEach { window in
|
||||
// Unfortunately .preferredColorScheme doesn't propagate properly throughout the app when changed
|
||||
window.overrideUserInterfaceStyle = appAppearance.interfaceStyle
|
||||
}
|
||||
windowManager.windows.forEach { window in
|
||||
// Unfortunately .preferredColorScheme doesn't propagate properly throughout the app when changed
|
||||
window.overrideUserInterfaceStyle = appAppearance.interfaceStyle
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func handlePotentialPhishingAttempt(url: URL, openURLAction: @escaping (URL) -> Void) -> Bool {
|
||||
@@ -450,7 +448,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
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) {
|
||||
guard let userDefaults = UserDefaults(suiteName: InfoPlistReader.main.appGroupIdentifier) else {
|
||||
return
|
||||
@@ -1200,12 +1198,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
// This is important for the app to keep refreshing in the background
|
||||
scheduleBackgroundAppRefresh()
|
||||
|
||||
/// 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
|
||||
/// 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
|
||||
/// isolated in the `stopSync` method above.
|
||||
/// https://sentry.tools.element.io/organizations/element/issues/4477794/
|
||||
// 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
|
||||
// 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
|
||||
// isolated in the `stopSync` method above.
|
||||
// https://sentry.tools.element.io/organizations/element/issues/4477794/
|
||||
task.expirationHandler = { @Sendable [weak self] in
|
||||
MXLog.info("Background app refresh task is about to expire.")
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class AppMediator: AppMediatorProtocol {
|
||||
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 {
|
||||
UIApplication.shared
|
||||
}
|
||||
|
||||
@@ -41,6 +41,11 @@ struct CommonFlowParameters {
|
||||
let notificationManager: NotificationManagerProtocol
|
||||
let stateMachineFactory: StateMachineFactoryProtocol
|
||||
|
||||
var windowManager: WindowManagerProtocol { appMediator.windowManager }
|
||||
var ongoingCallRoomIDPublisher: CurrentValuePublisher<String?, Never> { elementCallService.ongoingCallRoomIDPublisher }
|
||||
var windowManager: WindowManagerProtocol {
|
||||
appMediator.windowManager
|
||||
}
|
||||
|
||||
var ongoingCallRoomIDPublisher: CurrentValuePublisher<String?, Never> {
|
||||
elementCallService.ongoingCallRoomIDPublisher
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,13 @@ import SwiftUI
|
||||
let module: NavigationModule
|
||||
let details: TabDetails
|
||||
|
||||
var id: ObjectIdentifier { module.id }
|
||||
@MainActor var coordinator: CoordinatorProtocol? { module.coordinator }
|
||||
var id: ObjectIdentifier {
|
||||
module.id
|
||||
}
|
||||
|
||||
@MainActor var coordinator: CoordinatorProtocol? {
|
||||
module.coordinator
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var tabModules = [TabModule]() {
|
||||
|
||||
@@ -13,7 +13,7 @@ import EmbeddedElementCall
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
// Common settings between app and NSE
|
||||
/// Common settings between app and NSE
|
||||
protocol CommonSettingsProtocol {
|
||||
var logLevel: LogLevel { get }
|
||||
var traceLogPacks: Set<TraceLogPack> { get }
|
||||
@@ -265,7 +265,9 @@ final class AppSettings {
|
||||
}
|
||||
|
||||
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))
|
||||
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.
|
||||
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.
|
||||
var canPromptForAnalytics: Bool { analyticsConfiguration != nil || bugReportSentryURL != nil }
|
||||
var canPromptForAnalytics: Bool {
|
||||
analyticsConfiguration != nil || bugReportSentryURL != nil
|
||||
}
|
||||
|
||||
private static func makeAnalyticsConfiguration() -> AnalyticsConfiguration? {
|
||||
guard let host = Secrets.postHogHost, let apiKey = Secrets.postHogAPIKey else { return nil }
|
||||
@@ -364,7 +368,7 @@ final class AppSettings {
|
||||
|
||||
// MARK: - Maps
|
||||
|
||||
// maptiler base url
|
||||
/// maptiler base url
|
||||
private(set) var mapTilerConfiguration = MapTilerConfiguration(baseURL: "https://api.maptiler.com/maps",
|
||||
apiKey: Secrets.mapLibreAPIKey,
|
||||
lightStyleID: "9bc819c8-e627-474a-a348-ec144fe3d810",
|
||||
@@ -377,14 +381,14 @@ final class AppSettings {
|
||||
|
||||
// MARK: - Feature Flags
|
||||
|
||||
// Spaces
|
||||
/// Spaces
|
||||
@UserPreference(key: UserDefaultsKeys.spaceSettingsEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var spaceSettingsEnabled
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.createSpaceEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var createSpaceEnabled
|
||||
|
||||
// Others
|
||||
/// Others
|
||||
@UserPreference(key: UserDefaultsKeys.publicSearchEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var publicSearchEnabled
|
||||
|
||||
|
||||
@@ -22,5 +22,7 @@ struct MapTilerConfiguration {
|
||||
/// A MapLibre style ID for a dark-mode map.
|
||||
let darkStyleID: String
|
||||
|
||||
var isEnabled: Bool { apiKey != nil }
|
||||
var isEnabled: Bool {
|
||||
apiKey != nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,13 @@ import Combine
|
||||
class RemotePreference<T: Equatable> {
|
||||
private let defaultValue: T
|
||||
private let subject: CurrentValueSubject<T, Never>
|
||||
var publisher: CurrentValuePublisher<T, Never> { subject.asCurrentValuePublisher() }
|
||||
var isRemotelyConfigured: Bool { subject.value != defaultValue }
|
||||
var publisher: CurrentValuePublisher<T, Never> {
|
||||
subject.asCurrentValuePublisher()
|
||||
}
|
||||
|
||||
var isRemotelyConfigured: Bool {
|
||||
subject.value != defaultValue
|
||||
}
|
||||
|
||||
init(_ defaultValue: T) {
|
||||
self.defaultValue = defaultValue
|
||||
|
||||
@@ -24,14 +24,17 @@ final class UserPreference<T: Codable> {
|
||||
}
|
||||
|
||||
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 let defaultValue: T
|
||||
private let subject: PassthroughSubject<T, Never> = .init()
|
||||
private let mode: Mode
|
||||
|
||||
// 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`
|
||||
/// 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`
|
||||
var isLockedToRemote: Bool {
|
||||
mode == .remoteOverLocal && remoteValue != nil
|
||||
}
|
||||
@@ -53,7 +56,7 @@ final class UserPreference<T: Codable> {
|
||||
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 {
|
||||
get {
|
||||
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
|
||||
// So it can only be accessed by doing `AppSettings._preferenceName.remoteValue`
|
||||
/// 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`
|
||||
var remoteValue: T? {
|
||||
get {
|
||||
keyedStorage[remoteKey]
|
||||
|
||||
@@ -25,7 +25,9 @@ class ChatsTabFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let navigationSplitCoordinator: NavigationSplitCoordinator
|
||||
private let flowParameters: CommonFlowParameters
|
||||
|
||||
private var userSession: UserSessionProtocol { flowParameters.userSession }
|
||||
private var userSession: UserSessionProtocol {
|
||||
flowParameters.userSession
|
||||
}
|
||||
|
||||
private let stateMachine: ChatsTabFlowCoordinatorStateMachine
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ class MediaEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||
private let flowParameters: CommonFlowParameters
|
||||
|
||||
private var userSession: UserSessionProtocol { flowParameters.userSession }
|
||||
private var userSession: UserSessionProtocol {
|
||||
flowParameters.userSession
|
||||
}
|
||||
|
||||
private let actionsSubject: PassthroughSubject<MediaEventsTimelineFlowCoordinatorAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<MediaEventsTimelineFlowCoordinatorAction, Never> {
|
||||
|
||||
@@ -21,7 +21,9 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||
private let flowParameters: CommonFlowParameters
|
||||
|
||||
private var userSession: UserSessionProtocol { flowParameters.userSession }
|
||||
private var userSession: UserSessionProtocol {
|
||||
flowParameters.userSession
|
||||
}
|
||||
|
||||
private let actionsSubject: PassthroughSubject<PinnedEventsTimelineFlowCoordinatorAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<PinnedEventsTimelineFlowCoordinatorAction, Never> {
|
||||
|
||||
@@ -67,7 +67,9 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||
private let flowParameters: CommonFlowParameters
|
||||
|
||||
private var userSession: UserSessionProtocol { flowParameters.userSession }
|
||||
private var userSession: UserSessionProtocol {
|
||||
flowParameters.userSession
|
||||
}
|
||||
|
||||
private var roomProxy: JoinedRoomProxyProtocol!
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ final class SpaceSettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
/// The edit address screen
|
||||
case editAddress
|
||||
|
||||
// Other flows
|
||||
/// Other flows
|
||||
/// The roles and permissions screen
|
||||
case rolesAndPermissionsFlow
|
||||
/// The members flow screen
|
||||
|
||||
@@ -27,7 +27,9 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let appLockService: AppLockServiceProtocol
|
||||
private let flowParameters: CommonFlowParameters
|
||||
|
||||
private var userSession: UserSessionProtocol { flowParameters.userSession }
|
||||
private var userSession: UserSessionProtocol {
|
||||
flowParameters.userSession
|
||||
}
|
||||
|
||||
private let onboardingFlowCoordinator: OnboardingFlowCoordinator
|
||||
private let onboardingStackCoordinator: NavigationStackCoordinator
|
||||
@@ -145,12 +147,12 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
chatsTabFlowCoordinator.clearRoute(animated: animated)
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// or verification flows until they're complete… This needs more thought before we
|
||||
// codify it all into the state machine.
|
||||
/// 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
|
||||
/// 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
|
||||
/// or verification flows until they're complete… This needs more thought before we
|
||||
/// codify it all into the state machine.
|
||||
private func clearPresentedSheets(animated: Bool) {
|
||||
switch stateMachine.state {
|
||||
case .initial, .tabBar:
|
||||
|
||||
@@ -38,7 +38,7 @@ extension RoomMemberProxyMock {
|
||||
powerLevel = configuration.powerLevel
|
||||
}
|
||||
|
||||
// Mocks
|
||||
/// Mocks
|
||||
static var mockMe: RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@me:matrix.org",
|
||||
displayName: "Me",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
|
||||
extension UserProfileProxy {
|
||||
// Mocks
|
||||
/// Mocks
|
||||
static var mockAlice: UserProfileProxy {
|
||||
.init(userID: "@alice:matrix.org", displayName: "Alice", avatarURL: "mxc://matrix.org/UcCimidcvpFvWkPzvjXMQPHA")
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@ enum A11yIdentifiers {
|
||||
}
|
||||
|
||||
struct AppLockScreen {
|
||||
func numpad(_ digit: Int) -> String { "app_lock-numpad_\(digit)" }
|
||||
func numpad(_ digit: Int) -> String {
|
||||
"app_lock-numpad_\(digit)"
|
||||
}
|
||||
}
|
||||
|
||||
struct AppLockSetupBiometricsScreen {
|
||||
|
||||
@@ -15,7 +15,6 @@ struct ScreenTrackerViewModifier: ViewModifier {
|
||||
|
||||
let screen: AnalyticsEvent.MobileScreen.ScreenName
|
||||
|
||||
@ViewBuilder
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.onAppear {
|
||||
|
||||
@@ -13,7 +13,7 @@ enum Avatars {
|
||||
enum Size {
|
||||
case user(on: UserAvatarSizeOnScreen)
|
||||
case room(on: RoomAvatarSizeOnScreen)
|
||||
// custom
|
||||
/// custom
|
||||
case custom(CGFloat)
|
||||
|
||||
/// Value in UIKit points
|
||||
|
||||
@@ -102,11 +102,9 @@ private func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float
|
||||
let quantG = (value / 19) % 19
|
||||
let quantB = value % 19
|
||||
|
||||
let rgb = (signPow((Float(quantR) - 9) / 9, 2) * maximumValue,
|
||||
signPow((Float(quantG) - 9) / 9, 2) * maximumValue,
|
||||
signPow((Float(quantB) - 9) / 9, 2) * maximumValue)
|
||||
|
||||
return rgb
|
||||
return (signPow((Float(quantR) - 9) / 9, 2) * maximumValue,
|
||||
signPow((Float(quantG) - 9) / 9, 2) * maximumValue,
|
||||
signPow((Float(quantB) - 9) / 9, 2) * maximumValue)
|
||||
}
|
||||
|
||||
private func signPow(_ value: Float, _ exp: Float) -> Float {
|
||||
|
||||
@@ -39,14 +39,12 @@ struct CollapsibleReactionLayout: Layout {
|
||||
rows: collapsedRows,
|
||||
collapseButton: subviewsByType.collapseButton,
|
||||
addMoreButton: subviewsByType.addMoreButton)
|
||||
let size = sizeThatFits(rows: collapsedRowsWithButtons)
|
||||
return size
|
||||
return sizeThatFits(rows: collapsedRowsWithButtons)
|
||||
} else {
|
||||
// Show all subviews with the button at the end
|
||||
var rowsWithButtons = calculateRows(proposal: proposal, subviews: Array(subviews))
|
||||
ensureCollapseAndAddMoreButtonsAreOnTheSameRow(&rowsWithButtons)
|
||||
let size = sizeThatFits(rows: rowsWithButtons)
|
||||
return size
|
||||
return sizeThatFits(rows: rowsWithButtons)
|
||||
}
|
||||
} else {
|
||||
// Otherwise we are just calculating the size of all items without the button
|
||||
|
||||
@@ -21,7 +21,7 @@ struct CurrentValuePublisher<Output, Failure: Error>: Publisher {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ protocol AlertProtocol {
|
||||
}
|
||||
|
||||
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: {
|
||||
item.wrappedValue != nil
|
||||
}, set: { newValue in
|
||||
@@ -25,7 +25,7 @@ extension View {
|
||||
}
|
||||
|
||||
// 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: {
|
||||
item.wrappedValue != nil
|
||||
}, set: { newValue in
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
|
||||
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 {
|
||||
String(characters[...])
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ protocol ConfirmationDialogProtocol {
|
||||
}
|
||||
|
||||
extension View {
|
||||
func confirmationDialog<Item, Actions>(item: Binding<Item?>,
|
||||
titleVisibility: Visibility = .automatic,
|
||||
@ViewBuilder actions: (Item) -> Actions) -> some View where Item: ConfirmationDialogProtocol, Actions: View {
|
||||
func confirmationDialog<Item: ConfirmationDialogProtocol, Actions: View>(item: Binding<Item?>,
|
||||
titleVisibility: Visibility = .automatic,
|
||||
@ViewBuilder actions: (Item) -> Actions) -> some View {
|
||||
let binding = Binding<Bool>(get: {
|
||||
item.wrappedValue != nil
|
||||
}, set: { newValue in
|
||||
@@ -27,10 +27,10 @@ extension View {
|
||||
}
|
||||
|
||||
// periphery: ignore - not used yet but might be useful
|
||||
func confirmationDialog<Item, Actions, Message>(item: Binding<Item?>,
|
||||
titleVisibility: Visibility = .automatic,
|
||||
@ViewBuilder actions: (Item) -> Actions,
|
||||
@ViewBuilder message: (Item) -> Message) -> some View where Item: ConfirmationDialogProtocol, Actions: View, Message: View {
|
||||
func confirmationDialog<Item: ConfirmationDialogProtocol, Actions: View, Message: View>(item: Binding<Item?>,
|
||||
titleVisibility: Visibility = .automatic,
|
||||
@ViewBuilder actions: (Item) -> Actions,
|
||||
@ViewBuilder message: (Item) -> Message) -> some View {
|
||||
let binding = Binding<Bool>(get: {
|
||||
item.wrappedValue != nil
|
||||
}, set: { newValue in
|
||||
|
||||
@@ -67,8 +67,8 @@ extension Date {
|
||||
}
|
||||
|
||||
private extension DateFormatter {
|
||||
// There doesn't appear to be a way to get "Today" out of
|
||||
// `Date.RelativeFormatStyle` so use the old way instead 😐
|
||||
/// There doesn't appear to be a way to get "Today" out of
|
||||
/// `Date.RelativeFormatStyle` so use the old way instead 😐
|
||||
static let relative: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.doesRelativeDateFormatting = true
|
||||
|
||||
@@ -197,5 +197,7 @@ extension NSItemProvider {
|
||||
}
|
||||
|
||||
private extension NSString {
|
||||
var hasPathExtension: Bool { !pathExtension.isEmpty }
|
||||
var hasPathExtension: Bool {
|
||||
!pathExtension.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import OrderedCollections
|
||||
|
||||
extension OrderedSet {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import SwiftUI
|
||||
|
||||
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 {
|
||||
Section {
|
||||
EmptyView()
|
||||
|
||||
@@ -135,12 +135,29 @@ extension URL {
|
||||
|
||||
// MARK: Mocks
|
||||
|
||||
static var mockMXCAudio: URL { "mxc://matrix.org/1234567890AuDiO" }
|
||||
static var mockMXCFile: URL { "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" }
|
||||
static var mockMXCAudio: URL {
|
||||
"mxc://matrix.org/1234567890AuDiO"
|
||||
}
|
||||
|
||||
static var mockMXCFile: URL {
|
||||
"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
|
||||
|
||||
@@ -96,7 +96,7 @@ extension XCTestCase {
|
||||
timeout: TimeInterval = 10,
|
||||
message: String? = nil) -> DeferredFulfillment<P.Output> {
|
||||
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]
|
||||
if let index = expectedOrder.firstIndex(where: { $0 == receivedValue }), index == 0 {
|
||||
expectedOrder.remove(at: index)
|
||||
@@ -104,8 +104,6 @@ extension XCTestCase {
|
||||
|
||||
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.
|
||||
@@ -120,15 +118,13 @@ extension XCTestCase {
|
||||
timeout: TimeInterval = 10,
|
||||
message: String? = nil) -> DeferredFulfillment<Value> {
|
||||
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 {
|
||||
expectedOrder.remove(at: index)
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@@ -72,13 +72,13 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
return result
|
||||
}
|
||||
|
||||
// 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
|
||||
// most of the time from a background thread.
|
||||
// Use DTCoreText HTML renderer instead.
|
||||
// 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
|
||||
// webview.
|
||||
/// 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
|
||||
/// most of the time from a background thread.
|
||||
/// Use DTCoreText HTML renderer instead.
|
||||
/// 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
|
||||
/// webview.
|
||||
func fromHTML(_ htmlString: String?) -> AttributedString? {
|
||||
guard let originalHTMLString = htmlString else {
|
||||
return nil
|
||||
|
||||
@@ -89,7 +89,9 @@ extension AttributeScopes {
|
||||
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
|
||||
|
||||
@@ -26,7 +26,9 @@ enum Tracing {
|
||||
/// 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.
|
||||
static var logsDirectoryOverride: URL?
|
||||
static var legacyLogsDirectory: URL { .appGroupContainerDirectory }
|
||||
static var legacyLogsDirectory: URL {
|
||||
.appGroupContainerDirectory
|
||||
}
|
||||
|
||||
static let fileExtension = "log"
|
||||
|
||||
@@ -60,7 +62,9 @@ enum Tracing {
|
||||
}
|
||||
|
||||
/// 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.
|
||||
private static func logFiles(in directory: URL) -> [URL] {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/*
|
||||
/**
|
||||
Behavior mode of the current user's location, can be hidden, only shown and shown following the user
|
||||
*/
|
||||
enum ShowUserLocationMode {
|
||||
|
||||
@@ -114,7 +114,9 @@ struct MapLibreStaticMapView_Previews: PreviewProvider, TestablePreview {
|
||||
}
|
||||
|
||||
private struct MapTilerURLBuilderMock: MapTilerURLBuilderProtocol {
|
||||
func interactiveMapURL(for style: MapTilerStyle) -> URL? { nil }
|
||||
func interactiveMapURL(for style: MapTilerStyle) -> URL? {
|
||||
nil
|
||||
}
|
||||
|
||||
func staticMapTileImageURL(for style: MapTilerStyle,
|
||||
coordinates: CLLocationCoordinate2D,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
// https://spec.matrix.org/latest/appendices/#identifier-grammar
|
||||
/// https://spec.matrix.org/latest/appendices/#identifier-grammar
|
||||
enum MatrixEntityRegex: String {
|
||||
case homeserver
|
||||
case userID
|
||||
|
||||
@@ -22,7 +22,7 @@ final class MessageTextView: UITextView, PillAttachmentViewProviderDelegate, UIG
|
||||
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 {
|
||||
if otherGestureRecognizer is UILongPressGestureRecognizer {
|
||||
return false
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import SwiftUI
|
||||
import SwiftUIIntrospect
|
||||
import UIKit
|
||||
|
||||
import WysiwygComposer
|
||||
|
||||
protocol PillAttachmentViewProviderDelegate: AnyObject {
|
||||
|
||||
@@ -29,7 +29,6 @@ struct PillView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var mainContent: some View {
|
||||
Text(context.viewState.displayText)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
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 {
|
||||
func handleEventOnRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, eventID: String, roomAlias: String) { }
|
||||
|
||||
|
||||
@@ -26,123 +26,177 @@ final class SDKListener<T> {
|
||||
// MARK: QRCodeLoginService
|
||||
|
||||
extension SDKListener: QrLoginProgressListener where T == QrLoginProgress {
|
||||
func onUpdate(state: QrLoginProgress) { onUpdateClosure(state) }
|
||||
func onUpdate(state: QrLoginProgress) {
|
||||
onUpdateClosure(state)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: GrantQrLoginProgressListener where T == GrantQrLoginProgress {
|
||||
func onUpdate(state: GrantQrLoginProgress) { onUpdateClosure(state) }
|
||||
func onUpdate(state: GrantQrLoginProgress) {
|
||||
onUpdateClosure(state)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: GrantGeneratedQrLoginProgressListener where T == GrantGeneratedQrLoginProgress {
|
||||
func onUpdate(state: GrantGeneratedQrLoginProgress) { onUpdateClosure(state) }
|
||||
func onUpdate(state: GrantGeneratedQrLoginProgress) {
|
||||
onUpdateClosure(state)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: ClientProxy
|
||||
|
||||
extension SDKListener: MediaPreviewConfigListener where T == MediaPreviewConfig? {
|
||||
func onChange(mediaPreviewConfig: MediaPreviewConfig?) { onUpdateClosure(mediaPreviewConfig) }
|
||||
func onChange(mediaPreviewConfig: MediaPreviewConfig?) {
|
||||
onUpdateClosure(mediaPreviewConfig)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: SyncServiceStateObserver where T == SyncServiceState {
|
||||
func onUpdate(state: SyncServiceState) { onUpdateClosure(state) }
|
||||
func onUpdate(state: SyncServiceState) {
|
||||
onUpdateClosure(state)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: RoomListServiceStateListener where T == RoomListServiceState {
|
||||
func onUpdate(state: RoomListServiceState) { onUpdateClosure(state) }
|
||||
func onUpdate(state: RoomListServiceState) {
|
||||
onUpdateClosure(state)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: RoomListServiceSyncIndicatorListener where T == RoomListServiceSyncIndicator {
|
||||
func onUpdate(syncIndicator: RoomListServiceSyncIndicator) { onUpdateClosure(syncIndicator) }
|
||||
func onUpdate(syncIndicator: RoomListServiceSyncIndicator) {
|
||||
onUpdateClosure(syncIndicator)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: VerificationStateListener where T == VerificationState {
|
||||
func onUpdate(status: VerificationState) { onUpdateClosure(status) }
|
||||
func onUpdate(status: VerificationState) {
|
||||
onUpdateClosure(status)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
func onError(roomId: String, error: ClientError) { onUpdateClosure((roomId, error)) }
|
||||
func onError(roomId: String, error: ClientError) {
|
||||
onUpdateClosure((roomId, error))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: SecureBackupController
|
||||
|
||||
extension SDKListener: BackupStateListener where T == BackupState {
|
||||
func onUpdate(status: BackupState) { onUpdateClosure(status) }
|
||||
func onUpdate(status: BackupState) {
|
||||
onUpdateClosure(status)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: RecoveryStateListener where T == RecoveryState {
|
||||
func onUpdate(status: RecoveryState) { onUpdateClosure(status) }
|
||||
func onUpdate(status: RecoveryState) {
|
||||
onUpdateClosure(status)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: EnableRecoveryProgressListener where T == EnableRecoveryProgress {
|
||||
func onUpdate(status: EnableRecoveryProgress) { onUpdateClosure(status) }
|
||||
func onUpdate(status: EnableRecoveryProgress) {
|
||||
onUpdateClosure(status)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: BackupSteadyStateListener where T == BackupUploadState {
|
||||
func onUpdate(status: BackupUploadState) { onUpdateClosure(status) }
|
||||
func onUpdate(status: BackupUploadState) {
|
||||
onUpdateClosure(status)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: RoomSummaryProvider
|
||||
|
||||
extension SDKListener: RoomListEntriesListener where T == [RoomListEntriesUpdate] {
|
||||
func onUpdate(roomEntriesUpdate: [RoomListEntriesUpdate]) { onUpdateClosure(roomEntriesUpdate) }
|
||||
func onUpdate(roomEntriesUpdate: [RoomListEntriesUpdate]) {
|
||||
onUpdateClosure(roomEntriesUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: RoomListLoadingStateListener where T == RoomListLoadingState {
|
||||
func onUpdate(state: RoomListLoadingState) { onUpdateClosure(state) }
|
||||
func onUpdate(state: RoomListLoadingState) {
|
||||
onUpdateClosure(state)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Spaces
|
||||
|
||||
extension SDKListener: SpaceServiceJoinedSpacesListener where T == [SpaceListUpdate] {
|
||||
func onUpdate(rooms: [SpaceListUpdate]) { onUpdateClosure(rooms) }
|
||||
func onUpdate(rooms: [SpaceListUpdate]) {
|
||||
onUpdateClosure(rooms)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: SpaceRoomListEntriesListener where T == [SpaceListUpdate] {
|
||||
func onUpdate(roomUpdates: [SpaceListUpdate]) { onUpdateClosure(roomUpdates) }
|
||||
func onUpdate(roomUpdates: [SpaceListUpdate]) {
|
||||
onUpdateClosure(roomUpdates)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: SpaceRoomListPaginationStateListener where T == SpaceRoomListPaginationState {
|
||||
func onUpdate(paginationState: SpaceRoomListPaginationState) { onUpdateClosure(paginationState) }
|
||||
func onUpdate(paginationState: SpaceRoomListPaginationState) {
|
||||
onUpdateClosure(paginationState)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: SpaceRoomListSpaceListener where T == SpaceRoom? {
|
||||
func onUpdate(space: SpaceRoom?) { onUpdateClosure(space) }
|
||||
func onUpdate(space: SpaceRoom?) {
|
||||
onUpdateClosure(space)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: SpaceServiceSpaceFiltersListener where T == [SpaceFilterUpdate] {
|
||||
func onUpdate(filterUpdates: [SpaceFilterUpdate]) { onUpdateClosure(filterUpdates) }
|
||||
func onUpdate(filterUpdates: [SpaceFilterUpdate]) {
|
||||
onUpdateClosure(filterUpdates)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Room
|
||||
|
||||
extension SDKListener: RoomInfoListener where T == RoomInfo {
|
||||
func call(roomInfo: RoomInfo) { onUpdateClosure(roomInfo) }
|
||||
func call(roomInfo: RoomInfo) {
|
||||
onUpdateClosure(roomInfo)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: CallDeclineListener where T == String {
|
||||
func call(declinerUserId: String) { onUpdateClosure(declinerUserId) }
|
||||
func call(declinerUserId: String) {
|
||||
onUpdateClosure(declinerUserId)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: TypingNotificationsListener where T == [String] {
|
||||
func call(typingUserIds: [String]) { onUpdateClosure(typingUserIds) }
|
||||
func call(typingUserIds: [String]) {
|
||||
onUpdateClosure(typingUserIds)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: IdentityStatusChangeListener where T == [IdentityStatusChange] {
|
||||
func call(identityStatusChange: [IdentityStatusChange]) { onUpdateClosure(identityStatusChange) }
|
||||
func call(identityStatusChange: [IdentityStatusChange]) {
|
||||
onUpdateClosure(identityStatusChange)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: KnockRequestsListener where T == [KnockRequest] {
|
||||
func call(joinRequests: [KnockRequest]) { onUpdateClosure(joinRequests) }
|
||||
func call(joinRequests: [KnockRequest]) {
|
||||
onUpdateClosure(joinRequests)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: TimelineProxy
|
||||
|
||||
extension SDKListener: PaginationStatusListener where T == RoomPaginationStatus {
|
||||
func onUpdate(status: RoomPaginationStatus) { onUpdateClosure(status) }
|
||||
func onUpdate(status: RoomPaginationStatus) {
|
||||
onUpdateClosure(status)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKListener: ProgressWatcher where T == Double {
|
||||
@@ -156,11 +210,15 @@ extension SDKListener: ProgressWatcher where T == Double {
|
||||
// MARK: TimelineItemProvider
|
||||
|
||||
extension SDKListener: TimelineListener where T == [TimelineDiff] {
|
||||
func onUpdate(diff: [TimelineDiff]) { onUpdateClosure(diff) }
|
||||
func onUpdate(diff: [TimelineDiff]) {
|
||||
onUpdateClosure(diff)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: RoomDirectorySearchProxy
|
||||
|
||||
extension SDKListener: RoomDirectorySearchEntriesListener where T == [RoomDirectorySearchEntryUpdate] {
|
||||
func onUpdate(roomEntriesUpdate: [RoomDirectorySearchEntryUpdate]) { onUpdateClosure(roomEntriesUpdate) }
|
||||
func onUpdate(roomEntriesUpdate: [RoomDirectorySearchEntryUpdate]) {
|
||||
onUpdateClosure(roomEntriesUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public extension Animation {
|
||||
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 {
|
||||
let animation: Animation = ProcessInfo.isRunningTests ? .noAnimation : self
|
||||
return animation.disabledIfReduceMotionEnabled()
|
||||
|
||||
@@ -10,7 +10,9 @@ import SwiftUI
|
||||
|
||||
extension ButtonStyle where Self == MenuSheetButtonStyle {
|
||||
/// 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
|
||||
|
||||
@@ -31,7 +31,6 @@ extension View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func mediaGalleryTimelineAspectRatio(imageInfo: ImageInfoProxy?) -> some View {
|
||||
aspectRatio(imageInfo?.aspectRatio, contentMode: .fill)
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ private struct SearchControllerModifier: ViewModifier {
|
||||
/// is `false`, checking if this value is `false` is pretty much meaningless.
|
||||
@State private var isSearching = false
|
||||
|
||||
@ViewBuilder
|
||||
func body(content: Content) -> some View {
|
||||
let text: Text? = if let placeholder {
|
||||
Text(placeholder)
|
||||
@@ -191,7 +190,9 @@ private struct SearchController: UIViewControllerRepresentable {
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) { fatalError() }
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
override func willMove(toParent parent: UIViewController?) {
|
||||
parent?.navigationItem.searchController = searchController
|
||||
|
||||
@@ -135,9 +135,7 @@ private struct LoadableImageContent<TransformerView: View, PlaceholderView: View
|
||||
ZStack {
|
||||
switch (contentLoader.content, shouldRender) {
|
||||
case (.image(let image), true):
|
||||
transformer(
|
||||
AnyView(Image(uiImage: image).resizable())
|
||||
)
|
||||
transformer(AnyView(Image(uiImage: image).resizable()))
|
||||
case (.gifData, true):
|
||||
transformer(AnyView(KFAnimatedImage(source: .provider(self))))
|
||||
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? {
|
||||
if let blurhash,
|
||||
// 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 {
|
||||
view.overlay {
|
||||
Image(systemSymbol: .playCircleFill)
|
||||
|
||||
@@ -17,7 +17,9 @@ struct UserProfileListRow: View {
|
||||
|
||||
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? {
|
||||
guard !isUnknownProfile else { return L10n.commonInviteUnknownProfile }
|
||||
|
||||
@@ -58,12 +58,10 @@ private struct VisualListItemLabelStyle: LabelStyle {
|
||||
struct VisualListItem_Previews: PreviewProvider, TestablePreview {
|
||||
static let strings = AnalyticsPromptScreenStrings(termsURL: ServiceLocator.shared.settings.analyticsTermsURL)
|
||||
|
||||
@ViewBuilder
|
||||
static var testImage1: some View {
|
||||
Image(systemName: "circle")
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
static var testImage2: some View {
|
||||
Image(systemName: "square")
|
||||
}
|
||||
|
||||
@@ -13,8 +13,13 @@ enum UserIndicatorType: Equatable {
|
||||
case toast(progress: UserIndicator.Progress?)
|
||||
case modal(progress: UserIndicator.Progress?, interactiveDismissDisabled: Bool, allowsInteraction: Bool)
|
||||
|
||||
static var toast: Self { .toast(progress: .none) }
|
||||
static var modal: Self { .modal(progress: .indeterminate, interactiveDismissDisabled: false, allowsInteraction: false) }
|
||||
static var toast: Self {
|
||||
.toast(progress: .none)
|
||||
}
|
||||
|
||||
static var modal: Self {
|
||||
.modal(progress: .indeterminate, interactiveDismissDisabled: false, allowsInteraction: false)
|
||||
}
|
||||
}
|
||||
|
||||
struct UserIndicator: Equatable, Identifiable {
|
||||
|
||||
@@ -76,28 +76,24 @@ struct UserIndicatorModalView_Previews: PreviewProvider, TestablePreview {
|
||||
VStack(spacing: 0) {
|
||||
UserIndicatorModalView(indicator: UserIndicator(type: .modal,
|
||||
title: "Successfully logged in",
|
||||
iconName: "checkmark")
|
||||
)
|
||||
iconName: "checkmark"))
|
||||
|
||||
UserIndicatorModalView(indicator: UserIndicator(type: .modal(progress: .published(CurrentValueSubject<Double, Never>(0.5).asCurrentValuePublisher()),
|
||||
interactiveDismissDisabled: false,
|
||||
allowsInteraction: false),
|
||||
title: "Successfully logged in",
|
||||
iconName: "checkmark")
|
||||
)
|
||||
iconName: "checkmark"))
|
||||
|
||||
UserIndicatorModalView(indicator: UserIndicator(type: .modal(progress: .none,
|
||||
interactiveDismissDisabled: false,
|
||||
allowsInteraction: false),
|
||||
title: "Successfully logged in",
|
||||
iconName: "checkmark")
|
||||
)
|
||||
iconName: "checkmark"))
|
||||
|
||||
UserIndicatorModalView(indicator: UserIndicator(type: .modal,
|
||||
title: "Successfully logged in",
|
||||
message: "You can now be happy.",
|
||||
iconName: "checkmark")
|
||||
)
|
||||
iconName: "checkmark"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ struct UserIndicatorPresenter: View {
|
||||
.animation(.elementDefault, value: userIndicatorController.activeIndicator)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func indicatorViewFor(indicator: UserIndicator?) -> some View {
|
||||
ZStack { // Need a container to properly animate transitions
|
||||
if let indicator {
|
||||
|
||||
@@ -83,10 +83,8 @@ private struct VoiceMessageButtonStyle: ButtonStyle {
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.foregroundColor(isEnabled ? .compound.textSecondary.opacity(configuration.isPressed ? 0.6 : 1) : .compound.iconDisabled)
|
||||
.background(
|
||||
Circle()
|
||||
.foregroundColor(configuration.isPressed ? .compound.bgSubtlePrimary : .compound.bgCanvasDefault)
|
||||
)
|
||||
.background(Circle()
|
||||
.foregroundColor(configuration.isPressed ? .compound.bgSubtlePrimary : .compound.bgCanvasDefault))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,8 +39,7 @@ private struct WaveformInteractionModifier: ViewModifier {
|
||||
isDragging = true
|
||||
let progress = dragGesture.location.x / geometry.size.width
|
||||
onSeek(max(0, min(progress, 1.0)))
|
||||
}
|
||||
)
|
||||
})
|
||||
.offset(x: -cursorInteractiveSize / 2, y: 0)
|
||||
}
|
||||
.gesture(SpatialTapGesture()
|
||||
|
||||
@@ -27,9 +27,15 @@ struct AppLockScreenViewState: BindableState {
|
||||
var bindings: AppLockScreenViewStateBindings
|
||||
|
||||
/// 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.
|
||||
var isSubtitleWarning: Bool { numberOfPINAttempts > 0 }
|
||||
var isSubtitleWarning: Bool {
|
||||
numberOfPINAttempts > 0
|
||||
}
|
||||
|
||||
/// The string shown in the screen's subtitle.
|
||||
var subtitle: String {
|
||||
if !isSubtitleWarning {
|
||||
|
||||
@@ -78,7 +78,7 @@ struct AppLockScreen: View {
|
||||
/// The row of dots showing how many digits have been entered.
|
||||
var pinInputField: some View {
|
||||
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
|
||||
Circle()
|
||||
.fill(context.viewState.numberOfDigitsEntered > 0 ? .compound.iconPrimary : .compound.bgSubtlePrimary)
|
||||
|
||||
@@ -117,7 +117,9 @@ struct AppLockScreenPINKeypad_Previews: PreviewProvider {
|
||||
@StateObject var model = PreviewModel()
|
||||
class PreviewModel: ObservableObject {
|
||||
@Published var pinCode = ""
|
||||
var output: String { pinCode.isEmpty ? "Enter code" : pinCode }
|
||||
var output: String {
|
||||
pinCode.isEmpty ? "Enter code" : pinCode
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
||||
@@ -17,9 +17,17 @@ struct AppLockSetupBiometricsScreenViewState: BindableState {
|
||||
/// The supported biometry type on this device.
|
||||
let biometryType: LABiometryType
|
||||
|
||||
var icon: SFSymbol { biometryType.systemSymbol }
|
||||
var title: String { L10n.screenAppLockSetupBiometricUnlockAllowTitle(biometryType.localizedString) }
|
||||
var subtitle: String { L10n.screenAppLockSetupBiometricUnlockSubtitle(biometryType.localizedString) }
|
||||
var icon: SFSymbol {
|
||||
biometryType.systemSymbol
|
||||
}
|
||||
|
||||
var title: String {
|
||||
L10n.screenAppLockSetupBiometricUnlockAllowTitle(biometryType.localizedString)
|
||||
}
|
||||
|
||||
var subtitle: String {
|
||||
L10n.screenAppLockSetupBiometricUnlockSubtitle(biometryType.localizedString)
|
||||
}
|
||||
}
|
||||
|
||||
enum AppLockSetupBiometricsScreenViewAction {
|
||||
|
||||
@@ -50,7 +50,10 @@ struct AppLockSetupPINScreenViewState: BindableState {
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
guard mode == .unlock else { return L10n.screenAppLockSetupPinContext(InfoPlistReader.main.bundleDisplayName) }
|
||||
if !isSubtitleWarning {
|
||||
|
||||
@@ -22,8 +22,13 @@ struct AppLockSetupSettingsScreenViewState: BindableState {
|
||||
let biometryType: LABiometryType
|
||||
var bindings: AppLockSetupSettingsScreenViewStateBindings
|
||||
|
||||
var supportsBiometrics: Bool { biometryType != .none }
|
||||
var enableBiometricsTitle: String { L10n.screenAppLockSetupBiometricUnlockAllowTitle(biometryType.localizedString) }
|
||||
var supportsBiometrics: Bool {
|
||||
biometryType != .none
|
||||
}
|
||||
|
||||
var enableBiometricsTitle: String {
|
||||
L10n.screenAppLockSetupBiometricUnlockAllowTitle(biometryType.localizedString)
|
||||
}
|
||||
}
|
||||
|
||||
struct AppLockSetupSettingsScreenViewStateBindings {
|
||||
|
||||
@@ -26,12 +26,14 @@ enum LoginScreenCoordinatorAction {
|
||||
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 {
|
||||
private let parameters: LoginScreenCoordinatorParameters
|
||||
private var viewModel: LoginScreenViewModelProtocol
|
||||
|
||||
private var authenticationService: AuthenticationServiceProtocol { parameters.authenticationService }
|
||||
private var authenticationService: AuthenticationServiceProtocol {
|
||||
parameters.authenticationService
|
||||
}
|
||||
|
||||
private let actionsSubject: PassthroughSubject<LoginScreenCoordinatorAction, Never> = .init()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
@@ -31,7 +31,9 @@ struct LoginScreenViewState: BindableState {
|
||||
var bindings = LoginScreenBindings()
|
||||
|
||||
/// 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.
|
||||
var hasValidCredentials: Bool {
|
||||
|
||||
@@ -103,7 +103,9 @@ class OIDCAuthenticationPresenter: NSObject {
|
||||
// MARK: ASWebAuthenticationPresentationContextProviding
|
||||
|
||||
extension OIDCAuthenticationPresenter: ASWebAuthenticationPresentationContextProviding {
|
||||
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { presentationAnchor }
|
||||
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
||||
presentationAnchor
|
||||
}
|
||||
}
|
||||
|
||||
extension ASWebAuthenticationSession.Callback {
|
||||
|
||||
@@ -103,9 +103,9 @@ struct ServerConfirmationScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
// This is such a hack. I hate it!
|
||||
// But… We're not in a List/Form, the compound picker doesn't
|
||||
// support icons and this screen's design might change so 🤷♂️.
|
||||
/// This is such a hack. I hate it!
|
||||
/// But… We're not in a List/Form, the compound picker doesn't
|
||||
/// support icons and this screen's design might change so 🤷♂️.
|
||||
private struct FakeInlinePicker: View {
|
||||
let items: [String]
|
||||
let icon: KeyPath<CompoundIcons, Image>
|
||||
|
||||
@@ -22,12 +22,14 @@ enum ServerSelectionScreenCoordinatorAction {
|
||||
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 {
|
||||
private let parameters: ServerSelectionScreenCoordinatorParameters
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
private var viewModel: ServerSelectionScreenViewModelProtocol
|
||||
private var authenticationService: AuthenticationServiceProtocol { parameters.authenticationService }
|
||||
private var authenticationService: AuthenticationServiceProtocol {
|
||||
parameters.authenticationService
|
||||
}
|
||||
|
||||
private let actionsSubject: PassthroughSubject<ServerSelectionScreenCoordinatorAction, Never> = .init()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
@@ -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 {
|
||||
private let parameters: SoftLogoutScreenCoordinatorParameters
|
||||
private var viewModel: SoftLogoutScreenViewModelProtocol
|
||||
private let actionsSubject: PassthroughSubject<SoftLogoutScreenCoordinatorResult, Never> = .init()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
private var authenticationService: AuthenticationServiceProtocol { parameters.authenticationService }
|
||||
private var authenticationService: AuthenticationServiceProtocol {
|
||||
parameters.authenticationService
|
||||
}
|
||||
|
||||
private var oidcPresenter: OIDCAuthenticationPresenter?
|
||||
|
||||
var actions: AnyPublisher<SoftLogoutScreenCoordinatorResult, Never> {
|
||||
|
||||
@@ -54,7 +54,9 @@ struct SoftLogoutScreenViewState: BindableState {
|
||||
var bindings: SoftLogoutScreenBindings
|
||||
|
||||
/// 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.
|
||||
var window: UIWindow?
|
||||
|
||||
@@ -20,7 +20,9 @@ struct AuthenticationStartLogo: View {
|
||||
/// The shape that the logo is composed on top of.
|
||||
private let outerShape = RoundedRectangle(cornerRadius: 44)
|
||||
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 {
|
||||
if hideBrandChrome {
|
||||
|
||||
@@ -83,12 +83,10 @@ final class BugReportScreenCoordinator: CoordinatorProtocol {
|
||||
private static let loadingIndicatorIdentifier = "\(BugReportScreenCoordinator.self)-Loading"
|
||||
|
||||
private func startLoading(label: String = L10n.commonLoading, progressPublisher: CurrentValuePublisher<Double, Never>) {
|
||||
parameters.userIndicatorController?.submitIndicator(
|
||||
UserIndicator(id: Self.loadingIndicatorIdentifier,
|
||||
type: .modal(progress: .published(progressPublisher), interactiveDismissDisabled: false, allowsInteraction: true),
|
||||
title: label,
|
||||
persistent: true)
|
||||
)
|
||||
parameters.userIndicatorController?.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
|
||||
type: .modal(progress: .published(progressPublisher), interactiveDismissDisabled: false, allowsInteraction: true),
|
||||
title: label,
|
||||
persistent: true))
|
||||
}
|
||||
|
||||
private func stopLoading() {
|
||||
|
||||
@@ -15,8 +15,13 @@ struct BugReportScreen: View {
|
||||
|
||||
@Bindable var context: BugReportScreenViewModel.Context
|
||||
|
||||
var canSendLogFiles: Bool { context.viewState.canSendLogFiles }
|
||||
var photosPickerTitle: String { context.viewState.screenshot == nil ? L10n.screenBugReportAttachScreenshot : L10n.screenBugReportEditScreenshot }
|
||||
var canSendLogFiles: Bool {
|
||||
context.viewState.canSendLogFiles
|
||||
}
|
||||
|
||||
var photosPickerTitle: String {
|
||||
context.viewState.screenshot == nil ? L10n.screenBugReportAttachScreenshot : L10n.screenBugReportEditScreenshot
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
@@ -89,7 +94,6 @@ struct BugReportScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var attachScreenshotSection: some View {
|
||||
Section {
|
||||
ListRow(kind: .custom {
|
||||
|
||||
@@ -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 func handleOutputDeviceSelected(deviceID: String) {
|
||||
|
||||
@@ -197,8 +197,8 @@ private struct CallView: UIViewRepresentable {
|
||||
}
|
||||
}
|
||||
|
||||
// This function is called by the webview output routing button
|
||||
// it allows to open the OS output selector using the hidden button.
|
||||
/// This function is called by the webview output routing button
|
||||
/// it allows to open the OS output selector using the hidden button.
|
||||
private func tapRoutePickerView() {
|
||||
guard let button = routePickerView.subviews.first(where: { $0 is UIButton }) as? UIButton else {
|
||||
return
|
||||
|
||||
@@ -45,7 +45,6 @@ struct EncryptionResetPasswordScreen: View {
|
||||
.onAppear { textFieldFocus = true }
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var passwordSection: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(L10n.commonPassword)
|
||||
|
||||
@@ -70,7 +70,6 @@ struct EncryptionResetScreen: View {
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(.compound.bgSubtleSecondary))
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func checkMarkItem(title: String, position: ListPosition, positive: Bool) -> some View {
|
||||
VisualListItem(title: title, position: position) {
|
||||
CompoundIcon(positive ? \.check : \.info)
|
||||
|
||||
@@ -106,10 +106,10 @@ private struct MediaPreviewViewController: UIViewControllerRepresentable {
|
||||
])
|
||||
}
|
||||
|
||||
// Don't use viewWillAppear due to the following warning:
|
||||
// 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
|
||||
// the view controller hierarchy before presenting from it. Will become a hard exception in a future release.
|
||||
/// Don't use viewWillAppear due to the following warning:
|
||||
/// 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
|
||||
/// the view controller hierarchy before presenting from it. Will become a hard exception in a future release.
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
@@ -153,7 +153,10 @@ private struct MediaPreviewViewController: UIViewControllerRepresentable {
|
||||
class MediaPreviewItem: NSObject, QLPreviewItem {
|
||||
let file: MediaFileHandleProxy
|
||||
|
||||
var previewItemURL: URL? { file.url }
|
||||
var previewItemURL: URL? {
|
||||
file.url
|
||||
}
|
||||
|
||||
let previewItemTitle: String?
|
||||
|
||||
init(file: MediaFileHandleProxy, title: String?) {
|
||||
|
||||
@@ -114,8 +114,13 @@ class TimelineMediaPreviewDataSource: NSObject, QLPreviewControllerDataSource {
|
||||
|
||||
// MARK: - QLPreviewControllerDataSource
|
||||
|
||||
var firstPreviewItemIndex: Int { backwardPadding }
|
||||
var lastPreviewItemIndex: Int { backwardPadding + previewItems.count - 1 }
|
||||
var firstPreviewItemIndex: Int {
|
||||
backwardPadding
|
||||
}
|
||||
|
||||
var lastPreviewItemIndex: Int {
|
||||
backwardPadding + previewItems.count - 1
|
||||
}
|
||||
|
||||
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
|
||||
previewItems.count + backwardPadding + forwardPadding
|
||||
|
||||
@@ -56,7 +56,10 @@ struct TimelineMediaPreviewViewState: BindableState {
|
||||
var dataSource: TimelineMediaPreviewDataSource
|
||||
|
||||
/// 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.
|
||||
var currentItemActions: TimelineItemMenuActions?
|
||||
|
||||
|
||||
@@ -101,10 +101,10 @@ private struct MediaPreviewViewController: UIViewControllerRepresentable {
|
||||
])
|
||||
}
|
||||
|
||||
// Don't use viewWillAppear due to the following warning:
|
||||
// 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
|
||||
// the view controller hierarchy before presenting from it. Will become a hard exception in a future release.
|
||||
/// Don't use viewWillAppear due to the following warning:
|
||||
/// 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
|
||||
/// the view controller hierarchy before presenting from it. Will become a hard exception in a future release.
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
|
||||
@@ -236,5 +236,7 @@ class TimelineMediaPreviewViewModel: TimelineMediaPreviewViewModelType {
|
||||
title: L10n.screenMediaDetailsNoMoreMediaToShow))
|
||||
}
|
||||
|
||||
private var statusIndicatorID: String { "\(Self.self)-Status" }
|
||||
private var statusIndicatorID: String {
|
||||
"\(Self.self)-Status"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,10 @@ class TimelineMediaPreviewController: QLPreviewController {
|
||||
|
||||
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? {
|
||||
if #available(iOS 26, *) {
|
||||
view.subviews.first?.subviews.last?.subviews.first
|
||||
@@ -33,8 +36,13 @@ class TimelineMediaPreviewController: QLPreviewController {
|
||||
}
|
||||
}
|
||||
|
||||
private var pageScrollView: UIScrollView? { view.firstScrollView() }
|
||||
private var captionView: UIView { captionHostingController.view }
|
||||
private var pageScrollView: UIScrollView? {
|
||||
view.firstScrollView()
|
||||
}
|
||||
|
||||
private var captionView: UIView {
|
||||
captionHostingController.view
|
||||
}
|
||||
|
||||
override var overrideUserInterfaceStyle: UIUserInterfaceStyle {
|
||||
get { .dark }
|
||||
@@ -255,7 +263,9 @@ class TimelineMediaPreviewController: QLPreviewController {
|
||||
|
||||
private struct HeaderView: View {
|
||||
@ObservedObject var context: TimelineMediaPreviewViewModel.Context
|
||||
private var currentItem: TimelineMediaPreviewItem { context.viewState.currentItem }
|
||||
private var currentItem: TimelineMediaPreviewItem {
|
||||
context.viewState.currentItem
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
switch currentItem {
|
||||
@@ -281,7 +291,9 @@ private struct HeaderView: View {
|
||||
|
||||
private struct DetailsButton: View {
|
||||
@ObservedObject var context: TimelineMediaPreviewViewModel.Context
|
||||
private var currentItem: TimelineMediaPreviewItem { context.viewState.currentItem }
|
||||
private var currentItem: TimelineMediaPreviewItem {
|
||||
context.viewState.currentItem
|
||||
}
|
||||
|
||||
var isHidden: Bool {
|
||||
switch currentItem {
|
||||
@@ -301,7 +313,9 @@ private struct DetailsButton: View {
|
||||
|
||||
private struct CaptionView: View {
|
||||
@ObservedObject var context: TimelineMediaPreviewViewModel.Context
|
||||
private var currentItem: TimelineMediaPreviewItem { context.viewState.currentItem }
|
||||
private var currentItem: TimelineMediaPreviewItem {
|
||||
context.viewState.currentItem
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if case let .media(mediaItem) = currentItem, let caption = mediaItem.caption {
|
||||
@@ -323,7 +337,9 @@ private struct CaptionView: View {
|
||||
|
||||
private struct DownloadIndicatorView: View {
|
||||
@ObservedObject var context: TimelineMediaPreviewViewModel.Context
|
||||
private var currentItem: TimelineMediaPreviewItem { context.viewState.currentItem }
|
||||
private var currentItem: TimelineMediaPreviewItem {
|
||||
context.viewState.currentItem
|
||||
}
|
||||
|
||||
private var shouldShowDownloadIndicator: Bool {
|
||||
switch currentItem {
|
||||
|
||||
@@ -11,7 +11,9 @@ import SwiftUI
|
||||
struct TimelineMediaPreviewFileExportPicker: UIViewControllerRepresentable {
|
||||
struct File: Identifiable {
|
||||
let url: URL
|
||||
var id: String { url.absoluteString }
|
||||
var id: String {
|
||||
url.absoluteString
|
||||
}
|
||||
}
|
||||
|
||||
let file: File
|
||||
|
||||
@@ -57,7 +57,6 @@ struct TimelineMediaPreviewRedactConfirmationView: View {
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var preview: some View {
|
||||
HStack(spacing: 12) {
|
||||
if let mediaSource = item.thumbnailMediaSource {
|
||||
|
||||
@@ -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 {
|
||||
bindings.isSearchFieldFocused && bindings.searchQuery.isEmpty
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@ private struct BloomModifier: ViewModifier {
|
||||
|
||||
@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 {
|
||||
content
|
||||
@@ -102,7 +104,9 @@ private struct OldBloomModifier: ViewModifier {
|
||||
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 {
|
||||
LinearGradient(gradient: .compound.subtle,
|
||||
@@ -117,9 +121,9 @@ private struct OldBloomModifier: ViewModifier {
|
||||
bloom.colorScheme == colorScheme && bloom.baseColor == .compound.gradientSubtleStop1
|
||||
}
|
||||
|
||||
// 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
|
||||
// that can result in further (unnecessary) renders being made.
|
||||
/// 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
|
||||
/// that can result in further (unnecessary) renders being made.
|
||||
class Bloom {
|
||||
var image: UIImage?
|
||||
var colorScheme: ColorScheme?
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
import MatrixRustSDK
|
||||
import OrderedCollections
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@ struct RoomListFiltersView: View {
|
||||
|
||||
/// 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.
|
||||
private var macScrollBarPadding: CGFloat { ProcessInfo.processInfo.isiOSAppOnMac ? 16 : 0 }
|
||||
private var macScrollBarPadding: CGFloat {
|
||||
ProcessInfo.processInfo.isiOSAppOnMac ? 16 : 0
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollViewReader { proxy in
|
||||
|
||||
@@ -92,7 +92,6 @@ struct HomeScreenInviteCell: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var textualContent: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(title)
|
||||
|
||||
@@ -65,7 +65,6 @@ struct HomeScreenKnockedCell: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var textualContent: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(title)
|
||||
|
||||
@@ -76,7 +76,6 @@ struct HomeScreenRoomCell: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var header: some View {
|
||||
HStack(alignment: .top, spacing: 16) {
|
||||
Text(room.name)
|
||||
@@ -93,7 +92,6 @@ struct HomeScreenRoomCell: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var footer: some View {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 0) {
|
||||
ZStack(alignment: .topLeading) {
|
||||
|
||||
@@ -20,7 +20,6 @@ struct HomeScreenRoomList: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var content: some View {
|
||||
ForEach(context.viewState.visibleRooms) { room in
|
||||
switch room.type {
|
||||
|
||||
@@ -56,7 +56,6 @@ struct JoinRoomScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var defaultView: some View {
|
||||
VStack(spacing: 16) {
|
||||
RoomAvatarImage(avatar: context.viewState.avatar ?? .room(id: "", name: nil, avatarURL: nil),
|
||||
@@ -140,7 +139,6 @@ struct JoinRoomScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var knockedView: some View {
|
||||
VStack(spacing: 16) {
|
||||
BigIcon(icon: \.checkCircleSolid, style: .successSolid)
|
||||
@@ -157,7 +155,6 @@ struct JoinRoomScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var knockMessage: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack(spacing: 0) {
|
||||
|
||||
@@ -36,8 +36,8 @@ struct KnockRequestsListScreenViewState: BindableState {
|
||||
var isKnockableRoom = true
|
||||
var handledEventIDs: Set<String> = []
|
||||
|
||||
// If all the permissions are denied or the join rule changes while we are in the view
|
||||
// we want to stop displaying any request
|
||||
/// If all the permissions are denied or the join rule changes while we are in the view
|
||||
/// we want to stop displaying any request
|
||||
var shouldDisplayRequests: Bool {
|
||||
!displayedRequests.isEmpty && isKnockableRoom && (canAccept || canDecline || canBan)
|
||||
}
|
||||
|
||||
@@ -80,7 +80,6 @@ struct KnockRequestCell: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var actions: some View {
|
||||
VStack(spacing: 16) {
|
||||
if onDecline != nil || onAccept != nil {
|
||||
@@ -174,7 +173,9 @@ private struct DisclosableText: View {
|
||||
}
|
||||
|
||||
extension KnockRequestCellInfo: Identifiable {
|
||||
var id: String { eventID }
|
||||
var id: String {
|
||||
eventID
|
||||
}
|
||||
}
|
||||
|
||||
struct KnockRequestCell_Previews: PreviewProvider, TestablePreview {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user