From 05453f76117865ee098bed76327a4ebf7c559fd9 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 8 Sep 2022 19:53:53 +0200 Subject: [PATCH 001/104] Device manager: add user sessions overview current session strings. --- Riot/Assets/en.lproj/Vector.strings | 8 ++++++++ Riot/Generated/Strings.swift | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 011a610a5..1a3db4cfa 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2360,11 +2360,19 @@ To enable access, tap Settings> Location and select Always"; "user_sessions_overview_other_sessions_section_title" = "OTHER SESSIONS"; "user_sessions_overview_other_sessions_section_info" = "For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore."; +"user_sessions_overview_current_session_section_title" = "CURRENT SESSION"; + "user_session_verified" = "Verified session"; "user_session_unverified" = "Unverified session"; "user_session_verified_short" = "Verified"; "user_session_unverified_short" = "Unverified"; +"user_session_verify_action" = "Verify session"; +"user_session_view_details" = "View details"; +"user_session_learn_more" = "Learn more"; +"user_session_verified_additional_info" = "Your current session is ready for secure messaging."; +"user_session_unverified_additional_info" = "Verify your current session for enhanced secure messaging."; + // First item is client name and second item is session display name "user_session_name" = "%@: %@"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index a1cff2734..cdb211930 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -8467,6 +8467,10 @@ public class VectorL10n: NSObject { public static func userSessionItemDetails(_ p1: String, _ p2: String) -> String { return VectorL10n.tr("Vector", "user_session_item_details", p1, p2) } + /// Learn more + public static var userSessionLearnMore: String { + return VectorL10n.tr("Vector", "user_session_learn_more") + } /// %@: %@ public static func userSessionName(_ p1: String, _ p2: String) -> String { return VectorL10n.tr("Vector", "user_session_name", p1, p2) @@ -8475,6 +8479,10 @@ public class VectorL10n: NSObject { public static var userSessionUnverified: String { return VectorL10n.tr("Vector", "user_session_unverified") } + /// Verify your current session for enhanced secure messaging. + public static var userSessionUnverifiedAdditionalInfo: String { + return VectorL10n.tr("Vector", "user_session_unverified_additional_info") + } /// Unverified public static var userSessionUnverifiedShort: String { return VectorL10n.tr("Vector", "user_session_unverified_short") @@ -8483,10 +8491,26 @@ public class VectorL10n: NSObject { public static var userSessionVerified: String { return VectorL10n.tr("Vector", "user_session_verified") } + /// Your current session is ready for secure messaging. + public static var userSessionVerifiedAdditionalInfo: String { + return VectorL10n.tr("Vector", "user_session_verified_additional_info") + } /// Verified public static var userSessionVerifiedShort: String { return VectorL10n.tr("Vector", "user_session_verified_short") } + /// Verify session + public static var userSessionVerifyAction: String { + return VectorL10n.tr("Vector", "user_session_verify_action") + } + /// View details + public static var userSessionViewDetails: String { + return VectorL10n.tr("Vector", "user_session_view_details") + } + /// CURRENT SESSION + public static var userSessionsOverviewCurrentSessionSectionTitle: String { + return VectorL10n.tr("Vector", "user_sessions_overview_current_session_section_title") + } /// For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore. public static var userSessionsOverviewOtherSessionsSectionInfo: String { return VectorL10n.tr("Vector", "user_sessions_overview_other_sessions_section_info") From 74dfeca34c50a46c8119574d65a1953feaec7624 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 8 Sep 2022 20:00:15 +0200 Subject: [PATCH 002/104] Create UserSessionCardView to display session informations. --- .../UserSessionCardView.swift | 168 ++++++++++++++++++ .../UserSessionCardViewData.swift | 80 +++++++++ 2 files changed, 248 insertions(+) create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardView.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardViewData.swift diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardView.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardView.swift new file mode 100644 index 000000000..855ffa9c1 --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardView.swift @@ -0,0 +1,168 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI +import DesignKit + +struct UserSessionCardView: View { + + @Environment(\.theme) var theme: ThemeSwiftUI + + var viewData: UserSessionCardViewData + + var onVerifyAction: ((String) -> (Void))? = nil + var onViewDetailsAction: ((String) -> (Void))? = nil + var onLearnMoreAction: (() -> (Void))? = nil + + private var verificationStatusImageName: String { + return viewData.isVerified ? Asset.Images.userSessionVerified.name : Asset.Images.userSessionUnverified.name + } + + private var verificationStatusText: String { + return viewData.isVerified ? VectorL10n.userSessionVerified : VectorL10n.userSessionUnverified + } + + private var verificationStatusColor: Color { + return viewData.isVerified ? theme.colors.accent : theme.colors.alert + } + + private var verificationStatusAdditionalInfoText: String { + return viewData.isVerified ? VectorL10n.userSessionVerifiedAdditionalInfo : VectorL10n.userSessionUnverifiedAdditionalInfo + } + + private var backgroundShape: RoundedRectangle { + return RoundedRectangle(cornerRadius: 8) + } + + private var showExtraInformations: Bool { + return viewData.isCurrentSessionDisplayMode == false && (viewData.lastActivityDateString.isEmptyOrNil == false || viewData.lastSeenIPInfo.isEmptyOrNil == false) + } + + var body: some View { + VStack() { + VStack(alignment: .center, spacing: 12) { + DeviceAvatarView(viewData: viewData.deviceAvatarViewData) + + Text(viewData.sessionName) + .font(theme.fonts.headline) + .foregroundColor(theme.colors.primaryContent) + .multilineTextAlignment(.center) + + HStack { + Image(verificationStatusImageName) + Text(verificationStatusText) + .font(theme.fonts.subheadline) + .foregroundColor(verificationStatusColor) + .multilineTextAlignment(.center) + } + + if viewData.isCurrentSessionDisplayMode { + Text(verificationStatusAdditionalInfoText) + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.secondaryContent) + .multilineTextAlignment(.center) + } else { + InlineTextButton(verificationStatusAdditionalInfoText + " %@", tappableText: VectorL10n.userSessionLearnMore) { + onLearnMoreAction?() + } + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.secondaryContent) + .multilineTextAlignment(.center) + } + + if self.showExtraInformations { + VStack(spacing: 2) { + if let lastActivityDateString = viewData.lastActivityDateString, lastActivityDateString.isEmpty == false { + Text(lastActivityDateString) + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.secondaryContent) + .multilineTextAlignment(.center) + } + + if let lastSeenIPInfo = viewData.lastSeenIPInfo, lastSeenIPInfo.isEmpty == false { + Text(lastSeenIPInfo) + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.secondaryContent) + .multilineTextAlignment(.center) + } + } + } + + if viewData.isVerified == false { + Button { + onVerifyAction?(viewData.sessionId) + } label: { + Text(VectorL10n.userSessionVerifyAction) + .font(theme.fonts.body) + } + .buttonStyle(PrimaryActionButtonStyle()) + .padding(.top, 4) + .padding(.bottom, 3) + } + + if viewData.isCurrentSessionDisplayMode { + Button { + onViewDetailsAction?(viewData.sessionId) + } label: { + Text(VectorL10n.userSessionViewDetails) + .font(theme.fonts.body) + .foregroundColor(theme.colors.accent) + } + .padding(.top, 4) + } + } + .padding(24) + } + .frame(maxWidth: .infinity) + .background(theme.colors.background) + .clipShape(self.backgroundShape) + .shapedBorder(color: theme.colors.quinaryContent, borderWidth: 1.0, shape: self.backgroundShape) + } +} + +struct UserSessionCardViewPreview: View { + + @Environment(\.theme) var theme: ThemeSwiftUI + + let viewData: UserSessionCardViewData + + init(isCurrentSessionInfo: Bool = false) { + let currentSessionInfo = UserSessionInfo(sessionId: "alice", sessionName: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + + viewData = UserSessionCardViewData(userSessionInfo: currentSessionInfo, isCurrentSessionDisplayMode: isCurrentSessionInfo) + } + + var body: some View { + VStack { + UserSessionCardView(viewData: self.viewData) + } + .frame(maxWidth: .infinity) + .background(theme.colors.system) + .padding() + } +} + +struct UserSessionCardView_Previews: PreviewProvider { + + static var previews: some View { + Group { + UserSessionCardViewPreview(isCurrentSessionInfo: true).theme(.light).preferredColorScheme(.light) + UserSessionCardViewPreview(isCurrentSessionInfo: true).theme(.dark).preferredColorScheme(.dark) + UserSessionCardViewPreview().theme(.light).preferredColorScheme(.light) + UserSessionCardViewPreview().theme(.dark).preferredColorScheme(.dark) + } + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardViewData.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardViewData.swift new file mode 100644 index 000000000..bbbbc71c0 --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardViewData.swift @@ -0,0 +1,80 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +/// View data for UserSessionCardView +struct UserSessionCardViewData { + + // MARK: - Constants + + private static let userSessionNameFormatter = UserSessionNameFormatter() + private static let lastActivityDateFormatter = UserSessionLastActivityFormatter() + + // MARK: - Properties + + var id: String { + return sessionId + } + + let sessionId: String + + let sessionName: String + + let isVerified: Bool + + let lastActivityDateString: String? + + let lastSeenIPInfo: String? + + let deviceAvatarViewData: DeviceAvatarViewData + + /// Indicate if the current user session is shown and to adpat the layout + let isCurrentSessionDisplayMode: Bool + + // MARK: - Setup + + init(sessionId: String, + sessionDisplayName: String?, + deviceType: DeviceType, + isVerified: Bool, + lastActivityDate: TimeInterval?, + lastSeenIP: String?, + isCurrentSessionDisplayMode: Bool = false) { + self.sessionId = sessionId + self.sessionName = Self.userSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName) + self.isVerified = isVerified + + var lastActivityDateString: String? + + if let lastActivityDate = lastActivityDate { + lastActivityDateString = Self.lastActivityDateFormatter.lastActivityDateString(from: lastActivityDate) + } + + self.lastActivityDateString = lastActivityDateString + self.lastSeenIPInfo = lastSeenIP + self.deviceAvatarViewData = DeviceAvatarViewData(deviceType: deviceType, isVerified: nil) + + self.isCurrentSessionDisplayMode = isCurrentSessionDisplayMode + } +} + +extension UserSessionCardViewData { + + init(userSessionInfo: UserSessionInfo, isCurrentSessionDisplayMode: Bool = false) { + self.init(sessionId: userSessionInfo.sessionId, sessionDisplayName: userSessionInfo.sessionName, deviceType: userSessionInfo.deviceType, isVerified: userSessionInfo.isVerified, lastActivityDate: userSessionInfo.lastSeenTimestamp, lastSeenIP: userSessionInfo.lastSeenIP, isCurrentSessionDisplayMode: isCurrentSessionDisplayMode) + } +} From a46f0c240ea491b3d2c9843ceefbc42b3ebc2889 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 8 Sep 2022 20:01:02 +0200 Subject: [PATCH 003/104] UserSessionsOverview: Handle current session section view. --- .../UserSessionsOverviewModels.swift | 2 +- .../UserSessionsOverviewViewModel.swift | 8 +++---- .../View/UserSessionsOverview.swift | 24 +++++++++++++++++-- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift index 5d01772fa..b5b408081 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift @@ -38,7 +38,7 @@ struct UserSessionsOverviewViewState: BindableState { var inactiveSessionsViewData: [UserSessionListItemViewData] - var currentSessionViewData: UserSessionListItemViewData? + var currentSessionViewData: UserSessionCardViewData? var otherSessionsViewData: [UserSessionListItemViewData] diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift index 0edaf96bb..fcc5d73f4 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift @@ -37,9 +37,9 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess init(userSessionsOverviewService: UserSessionsOverviewServiceProtocol) { self.userSessionsOverviewService = userSessionsOverviewService - let viewState = UserSessionsOverviewViewState(unverifiedSessionsViewData: [], inactiveSessionsViewData: [], currentSessionViewData: nil, otherSessionsViewData: []) + let initialViewState = UserSessionsOverviewViewState(unverifiedSessionsViewData: [], inactiveSessionsViewData: [], currentSessionViewData: nil, otherSessionsViewData: []) - super.init(initialViewState: viewState) + super.init(initialViewState: initialViewState) self.updateViewState(with: userSessionsOverviewService.lastOverviewData) } @@ -72,13 +72,13 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess let unverifiedSessionsViewData = self.userSessionListItemViewDataList(from: userSessionsViewData.unverifiedSessionsInfo) let inactiveSessionsViewData = self.userSessionListItemViewDataList(from: userSessionsViewData.inactiveSessionsInfo) - var currentSessionViewData: UserSessionListItemViewData? + var currentSessionViewData: UserSessionCardViewData? let otherSessionsViewData = self.userSessionListItemViewDataList(from: userSessionsViewData.otherSessionsInfo) if let currentSessionInfo = userSessionsViewData.currentSessionInfo { - currentSessionViewData = UserSessionListItemViewData(userSessionInfo: currentSessionInfo) + currentSessionViewData = UserSessionCardViewData(userSessionInfo: currentSessionInfo, isCurrentSessionDisplayMode: true) } self.state.unverifiedSessionsViewData = unverifiedSessionsViewData diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift index f4b49c4ea..4e32d1dbf 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift @@ -39,7 +39,7 @@ struct UserSessionsOverview: View { // Current session section if let currentSessionViewData = viewModel.viewState.currentSessionViewData { - // TODO: + self.currentSessionsSection(currentSessionViewData: currentSessionViewData) } // Other sessions section @@ -56,7 +56,26 @@ struct UserSessionsOverview: View { } } - var otherSessionsSection: some View { + private func currentSessionsSection(currentSessionViewData: UserSessionCardViewData) -> some View { + SwiftUI.Section { + UserSessionCardView(viewData: currentSessionViewData, onVerifyAction: { _ in + viewModel.send(viewAction: .verifyCurrentSession) + }, onViewDetailsAction: { _ in + viewModel.send(viewAction: .viewCurrentSessionDetails) + }) + .padding(.horizontal, 16) + } header: { + Text(VectorL10n.userSessionsOverviewCurrentSessionSectionTitle) + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.secondaryContent) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 16) + .padding(.top, 24) + .padding(.bottom, 11) + } + } + + private var otherSessionsSection: some View { SwiftUI.Section { // Device list @@ -81,6 +100,7 @@ struct UserSessionsOverview: View { .padding(.bottom, 11) } .padding(.horizontal, 16) + .padding(.top, 24) } } } From ed036c75d3b4dbf84117ff7484e8373991f5b6e4 Mon Sep 17 00:00:00 2001 From: Doug Date: Mon, 12 Sep 2022 17:14:53 +0100 Subject: [PATCH 004/104] Prepare for new sprint --- Config/AppVersion.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index fbe364eb0..160b9dc81 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.9.5 -CURRENT_PROJECT_VERSION = 1.9.5 +MARKETING_VERSION = 1.9.6 +CURRENT_PROJECT_VERSION = 1.9.6 From 2115e8a8db33cdc6216823eec403228a05e7daf6 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 13 Sep 2022 12:24:25 +0100 Subject: [PATCH 005/104] Dismiss the launching screen when clearing cache. --- .../Home/AllChats/AllChatsViewController.swift | 17 ++++++++++++++++- changelog.d/6709.bugfix | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 changelog.d/6709.bugfix diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index 652426e32..5e8f5fafd 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -208,7 +208,22 @@ class AllChatsViewController: HomeViewController { override func addMatrixSession(_ mxSession: MXSession!) { super.addMatrixSession(mxSession) - initDataSource() + + if let dataSource = dataSource, !dataSource.mxSessions.contains(where: { $0 as? MXSession == mxSession }) { + dataSource.addMatrixSession(mxSession) + // Setting the delegate is required to send a RecentsViewControllerDataReadyNotification. + // Without this, when clearing the cache we end up with an infinite green spinner. + (dataSource as? RecentsDataSource)?.setDelegate(self, andRecentsDataSourceMode: recentsDataSourceMode) + } else { + initDataSource() + } + } + + override func removeMatrixSession(_ mxSession: MXSession!) { + super.removeMatrixSession(mxSession) + + guard let dataSource = dataSource else { return } + dataSource.removeMatrixSession(mxSession) } private func initDataSource() { diff --git a/changelog.d/6709.bugfix b/changelog.d/6709.bugfix new file mode 100644 index 000000000..3d6e2d0e6 --- /dev/null +++ b/changelog.d/6709.bugfix @@ -0,0 +1 @@ +New App Layout: Make sure the green loading spinner is dismissed after clearing the cache. From af8612b04e5d4069637d2a52c4e9edc04ff9546f Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 13 Sep 2022 13:17:44 +0100 Subject: [PATCH 006/104] Fix crash when previewing a room. --- Riot/Generated/Images.swift | 3 --- Riot/Modules/Home/AllChats/AllChatsCoordinator.swift | 2 +- Riot/Modules/Home/AllChats/AllChatsViewController.swift | 4 ++-- changelog.d/6712.bugfix | 1 + 4 files changed, 4 insertions(+), 6 deletions(-) create mode 100644 changelog.d/6712.bugfix diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 5b0b1d986..6fc13773b 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -115,8 +115,6 @@ internal class Asset: NSObject { internal static let encryptionWarning = ImageAsset(name: "encryption_warning") internal static let favouritesEmptyScreenArtwork = ImageAsset(name: "favourites_empty_screen_artwork") internal static let favouritesEmptyScreenArtworkDark = ImageAsset(name: "favourites_empty_screen_artwork_dark") - internal static let allChatRecents = ImageAsset(name: "all_chat_recents") - internal static let allChatUnreads = ImageAsset(name: "all_chat_unreads") internal static let roomActionDirectChat = ImageAsset(name: "room_action_direct_chat") internal static let roomActionFavourite = ImageAsset(name: "room_action_favourite") internal static let roomActionLeave = ImageAsset(name: "room_action_leave") @@ -124,7 +122,6 @@ internal class Asset: NSObject { internal static let roomActionNotificationMuted = ImageAsset(name: "room_action_notification_muted") internal static let roomActionPriorityHigh = ImageAsset(name: "room_action_priority_high") internal static let roomActionPriorityLow = ImageAsset(name: "room_action_priority_low") - internal static let allChatEditLayout = ImageAsset(name: "all_chat_edit_layout") internal static let allChatsEditIcon = ImageAsset(name: "all_chats_edit_icon") internal static let allChatsEmptyListPlaceholderIcon = ImageAsset(name: "all_chats_empty_list_placeholder_icon") internal static let allChatsEmptyScreenArtwork = ImageAsset(name: "all_chats_empty_screen_artwork") diff --git a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift index 410625b02..803ef5b71 100644 --- a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift +++ b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift @@ -767,7 +767,7 @@ extension AllChatsCoordinator: AllChatsViewControllerDelegate { self.showRoom(withNavigationParameters: roomNavigationParameters, completion: completion) } - func allChatsViewController(_ allChatsViewController: AllChatsViewController, didSelectRoomPreviewWithParameters roomPreviewNavigationParameters: RoomPreviewNavigationParameters, completion: @escaping () -> Void) { + func allChatsViewController(_ allChatsViewController: AllChatsViewController, didSelectRoomPreviewWithParameters roomPreviewNavigationParameters: RoomPreviewNavigationParameters, completion: (() -> Void)?) { self.showRoomPreview(withNavigationParameters: roomPreviewNavigationParameters, completion: completion) } diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index 5e8f5fafd..7d33aa1fc 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -22,7 +22,7 @@ import Reusable protocol AllChatsViewControllerDelegate: AnyObject { func allChatsViewControllerDidCompleteAuthentication(_ allChatsViewController: AllChatsViewController) func allChatsViewController(_ allChatsViewController: AllChatsViewController, didSelectRoomWithParameters roomNavigationParameters: RoomNavigationParameters, completion: @escaping () -> Void) - func allChatsViewController(_ allChatsViewController: AllChatsViewController, didSelectRoomPreviewWithParameters roomPreviewNavigationParameters: RoomPreviewNavigationParameters, completion: @escaping () -> Void) + func allChatsViewController(_ allChatsViewController: AllChatsViewController, didSelectRoomPreviewWithParameters roomPreviewNavigationParameters: RoomPreviewNavigationParameters, completion: (() -> Void)?) func allChatsViewController(_ allChatsViewController: AllChatsViewController, didSelectContact contact: MXKContact, with presentationParameters: ScreenPresentationParameters) } @@ -869,7 +869,7 @@ extension AllChatsViewController: SplitViewMasterViewControllerProtocol { /// - Parameters: /// - parameters: the presentation parameters that contains room preview information plus display information. /// - completion: the block to execute at the end of the operation. - func selectRoomPreview(with parameters: RoomPreviewNavigationParameters, completion: @escaping () -> Void) { + func selectRoomPreview(with parameters: RoomPreviewNavigationParameters, completion: (() -> Void)?) { releaseSelectedItem() let roomPreviewData = parameters.previewData diff --git a/changelog.d/6712.bugfix b/changelog.d/6712.bugfix new file mode 100644 index 000000000..c1276e052 --- /dev/null +++ b/changelog.d/6712.bugfix @@ -0,0 +1 @@ +Fix a crash when previewing a room. \ No newline at end of file From 1722a34f14f0dc80fb9f716a8b27fd1f9a45aa62 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 14 Sep 2022 08:42:14 +0100 Subject: [PATCH 007/104] Fix typo in empty state --- Riot/Assets/en.lproj/Vector.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index f346e5586..9546c6943 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2187,7 +2187,7 @@ Tap the + to start adding people."; "all_chats_empty_space_information" = "Spaces are a new way to group rooms and people. Add an existing room, or create a new one, using the bottom-right button."; "all_chats_empty_view_information" = "The all-in-one secure chat app for teams, friends and organisations. Create a chat, or join an existing room, to get started."; "all_chats_empty_list_placeholder_title" = "You’re all caught up."; -"all_chats_empty_unreads_placeholder_message" = "This is where you're unread messages will show up, when you have some."; +"all_chats_empty_unreads_placeholder_message" = "This is where your unread messages will show up, when you have some."; "all_chats_nothing_found_placeholder_title" = "Nothing found."; "all_chats_nothing_found_placeholder_message" = "Try adjusting your search."; From 8a5a8b4eec87175573d3995ae8a63f1300a10e93 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Wed, 14 Sep 2022 11:28:47 +0300 Subject: [PATCH 008/104] Code review fixes --- .../UserSessionCardView.swift | 126 +++++++++--------- .../UserSessionCardViewData.swift | 8 +- .../View/UserSessionsOverview.swift | 45 ++++--- 3 files changed, 89 insertions(+), 90 deletions(-) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardView.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardView.swift index 855ffa9c1..7032d5701 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardView.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardView.swift @@ -52,80 +52,78 @@ struct UserSessionCardView: View { } var body: some View { - VStack() { - VStack(alignment: .center, spacing: 12) { - DeviceAvatarView(viewData: viewData.deviceAvatarViewData) - - Text(viewData.sessionName) - .font(theme.fonts.headline) - .foregroundColor(theme.colors.primaryContent) + VStack(alignment: .center, spacing: 12) { + DeviceAvatarView(viewData: viewData.deviceAvatarViewData) + + Text(viewData.sessionName) + .font(theme.fonts.headline) + .foregroundColor(theme.colors.primaryContent) + .multilineTextAlignment(.center) + + HStack { + Image(verificationStatusImageName) + Text(verificationStatusText) + .font(theme.fonts.subheadline) + .foregroundColor(verificationStatusColor) .multilineTextAlignment(.center) - - HStack { - Image(verificationStatusImageName) - Text(verificationStatusText) - .font(theme.fonts.subheadline) - .foregroundColor(verificationStatusColor) - .multilineTextAlignment(.center) - } - - if viewData.isCurrentSessionDisplayMode { - Text(verificationStatusAdditionalInfoText) - .font(theme.fonts.footnote) - .foregroundColor(theme.colors.secondaryContent) - .multilineTextAlignment(.center) - } else { - InlineTextButton(verificationStatusAdditionalInfoText + " %@", tappableText: VectorL10n.userSessionLearnMore) { - onLearnMoreAction?() - } + } + + if viewData.isCurrentSessionDisplayMode { + Text(verificationStatusAdditionalInfoText) .font(theme.fonts.footnote) .foregroundColor(theme.colors.secondaryContent) .multilineTextAlignment(.center) + } else { + InlineTextButton(verificationStatusAdditionalInfoText + " %@", tappableText: VectorL10n.userSessionLearnMore) { + onLearnMoreAction?() } - - if self.showExtraInformations { - VStack(spacing: 2) { - if let lastActivityDateString = viewData.lastActivityDateString, lastActivityDateString.isEmpty == false { - Text(lastActivityDateString) - .font(theme.fonts.footnote) - .foregroundColor(theme.colors.secondaryContent) - .multilineTextAlignment(.center) - } - - if let lastSeenIPInfo = viewData.lastSeenIPInfo, lastSeenIPInfo.isEmpty == false { - Text(lastSeenIPInfo) - .font(theme.fonts.footnote) - .foregroundColor(theme.colors.secondaryContent) - .multilineTextAlignment(.center) - } + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.secondaryContent) + .multilineTextAlignment(.center) + } + + if self.showExtraInformations { + VStack(spacing: 2) { + if let lastActivityDateString = viewData.lastActivityDateString, lastActivityDateString.isEmpty == false { + Text(lastActivityDateString) + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.secondaryContent) + .multilineTextAlignment(.center) } - } - - if viewData.isVerified == false { - Button { - onVerifyAction?(viewData.sessionId) - } label: { - Text(VectorL10n.userSessionVerifyAction) - .font(theme.fonts.body) + + if let lastSeenIPInfo = viewData.lastSeenIPInfo, lastSeenIPInfo.isEmpty == false { + Text(lastSeenIPInfo) + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.secondaryContent) + .multilineTextAlignment(.center) } - .buttonStyle(PrimaryActionButtonStyle()) - .padding(.top, 4) - .padding(.bottom, 3) - } - - if viewData.isCurrentSessionDisplayMode { - Button { - onViewDetailsAction?(viewData.sessionId) - } label: { - Text(VectorL10n.userSessionViewDetails) - .font(theme.fonts.body) - .foregroundColor(theme.colors.accent) - } - .padding(.top, 4) } } - .padding(24) + + if viewData.isVerified == false { + Button { + onVerifyAction?(viewData.sessionId) + } label: { + Text(VectorL10n.userSessionVerifyAction) + .font(theme.fonts.body) + } + .buttonStyle(PrimaryActionButtonStyle()) + .padding(.top, 4) + .padding(.bottom, 3) + } + + if viewData.isCurrentSessionDisplayMode { + Button { + onViewDetailsAction?(viewData.sessionId) + } label: { + Text(VectorL10n.userSessionViewDetails) + .font(theme.fonts.body) + .foregroundColor(theme.colors.accent) + } + .padding(.top, 4) + } } + .padding(24) .frame(maxWidth: .infinity) .background(theme.colors.background) .clipShape(self.backgroundShape) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardViewData.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardViewData.swift index bbbbc71c0..d63175a0e 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardViewData.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardViewData.swift @@ -51,7 +51,7 @@ struct UserSessionCardViewData { sessionDisplayName: String?, deviceType: DeviceType, isVerified: Bool, - lastActivityDate: TimeInterval?, + lastActivityTimestamp: TimeInterval?, lastSeenIP: String?, isCurrentSessionDisplayMode: Bool = false) { self.sessionId = sessionId @@ -60,8 +60,8 @@ struct UserSessionCardViewData { var lastActivityDateString: String? - if let lastActivityDate = lastActivityDate { - lastActivityDateString = Self.lastActivityDateFormatter.lastActivityDateString(from: lastActivityDate) + if let lastActivityTimestamp = lastActivityTimestamp { + lastActivityDateString = Self.lastActivityDateFormatter.lastActivityDateString(from: lastActivityTimestamp) } self.lastActivityDateString = lastActivityDateString @@ -75,6 +75,6 @@ struct UserSessionCardViewData { extension UserSessionCardViewData { init(userSessionInfo: UserSessionInfo, isCurrentSessionDisplayMode: Bool = false) { - self.init(sessionId: userSessionInfo.sessionId, sessionDisplayName: userSessionInfo.sessionName, deviceType: userSessionInfo.deviceType, isVerified: userSessionInfo.isVerified, lastActivityDate: userSessionInfo.lastSeenTimestamp, lastSeenIP: userSessionInfo.lastSeenIP, isCurrentSessionDisplayMode: isCurrentSessionDisplayMode) + self.init(sessionId: userSessionInfo.sessionId, sessionDisplayName: userSessionInfo.sessionName, deviceType: userSessionInfo.deviceType, isVerified: userSessionInfo.isVerified, lastActivityTimestamp: userSessionInfo.lastSeenTimestamp, lastSeenIP: userSessionInfo.lastSeenIP, isCurrentSessionDisplayMode: isCurrentSessionDisplayMode) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift index 4e32d1dbf..fbfb70c3f 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift @@ -24,6 +24,28 @@ struct UserSessionsOverview: View { @Environment(\.theme) private var theme: ThemeSwiftUI + @ViewBuilder + private var currentSessionsSection: some View { + if let currentSessionViewData = viewModel.viewState.currentSessionViewData { + SwiftUI.Section { + UserSessionCardView(viewData: currentSessionViewData, onVerifyAction: { _ in + viewModel.send(viewAction: .verifyCurrentSession) + }, onViewDetailsAction: { _ in + viewModel.send(viewAction: .viewCurrentSessionDetails) + }) + .padding(.horizontal, 16) + } header: { + Text(VectorL10n.userSessionsOverviewCurrentSessionSectionTitle) + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.secondaryContent) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 16) + .padding(.top, 24) + .padding(.bottom, 11) + } + } + } + // MARK: Public @ObservedObject var viewModel: UserSessionsOverviewViewModel.Context @@ -38,9 +60,7 @@ struct UserSessionsOverview: View { } // Current session section - if let currentSessionViewData = viewModel.viewState.currentSessionViewData { - self.currentSessionsSection(currentSessionViewData: currentSessionViewData) - } + currentSessionsSection // Other sessions section if viewModel.viewState.otherSessionsViewData.isEmpty == false { @@ -56,25 +76,6 @@ struct UserSessionsOverview: View { } } - private func currentSessionsSection(currentSessionViewData: UserSessionCardViewData) -> some View { - SwiftUI.Section { - UserSessionCardView(viewData: currentSessionViewData, onVerifyAction: { _ in - viewModel.send(viewAction: .verifyCurrentSession) - }, onViewDetailsAction: { _ in - viewModel.send(viewAction: .viewCurrentSessionDetails) - }) - .padding(.horizontal, 16) - } header: { - Text(VectorL10n.userSessionsOverviewCurrentSessionSectionTitle) - .font(theme.fonts.footnote) - .foregroundColor(theme.colors.secondaryContent) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 16) - .padding(.top, 24) - .padding(.bottom, 11) - } - } - private var otherSessionsSection: some View { SwiftUI.Section { From 63a969c1ce3039cc59349d86006aa653635b613a Mon Sep 17 00:00:00 2001 From: David Langley Date: Wed, 14 Sep 2022 14:33:27 +0100 Subject: [PATCH 009/104] Update swift-ogg and add changelog --- Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- changelog.d/6584.bugfix | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/6584.bugfix diff --git a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved index a4ed2f4c0..3d248ff51 100644 --- a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -42,7 +42,7 @@ "location" : "https://github.com/vector-im/swift-ogg", "state" : { "branch" : "main", - "revision" : "0ffad3f7b45a6a4760db090d503b00f094bbecc0" + "revision" : "e9a9e7601da662fd8b97d93781ff5c60b4becf88" } } ], diff --git a/changelog.d/6584.bugfix b/changelog.d/6584.bugfix new file mode 100644 index 000000000..7fefaaff1 --- /dev/null +++ b/changelog.d/6584.bugfix @@ -0,0 +1 @@ +Fix crash when some opus audio files are added to a room. From b75580a11f3c86b0620da9d78de4cb54c0e17686 Mon Sep 17 00:00:00 2001 From: aringenbach Date: Thu, 15 Sep 2022 09:31:32 +0200 Subject: [PATCH 010/104] Fix crash on logout from too much wrong pin codes --- Riot/Modules/Application/LegacyAppDelegate.m | 13 +++++++++++-- .../Service/MatrixSDK/RecentsListService.swift | 8 ++++++++ changelog.d/6724.bugfix | 1 + 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 changelog.d/6724.bugfix diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index 865a01002..718669fa4 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -2162,6 +2162,9 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni - (void)logoutSendingRequestServer:(BOOL)sendLogoutServerRequest completion:(void (^)(BOOL isLoggedOut))completion { + MXSession *mainSession = self.mxSessions.firstObject; + [mainSession close]; + [self.pushNotificationService deregisterRemoteNotifications]; // Clear cache @@ -4360,8 +4363,14 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni { if (dueToTooManyErrors) { - [self showAlertWithTitle:nil message:[VectorL10n pinProtectionKickUserAlertMessage]]; - [self logoutWithConfirmation:NO completion:nil]; + [coordinatorBridgePresenter dismissWithMainAppWindow:self.window]; + self.setPinCoordinatorBridgePresenter = nil; + [self logoutWithConfirmation:NO completion:^(BOOL isLoggedOut) { + if (isLoggedOut) + { + [self showAlertWithTitle:nil message:[VectorL10n pinProtectionKickUserAlertMessage]]; + } + }]; } else { diff --git a/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift b/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift index 74effcd99..938361d00 100644 --- a/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift +++ b/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift @@ -638,6 +638,14 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { guard let session = session else { return } + guard session.state != .closed else { + MXLog.debug("[RecentsListService] createFetchers cancelled on closed session") + return + } + guard session.roomListDataManager != nil else { + MXLog.debug("[RecentsListService] createFetchers cancelled on race condition (session closing in progress)") + return + } guard session.isEventStreamInitialised else { return } diff --git a/changelog.d/6724.bugfix b/changelog.d/6724.bugfix new file mode 100644 index 000000000..f1aa01659 --- /dev/null +++ b/changelog.d/6724.bugfix @@ -0,0 +1 @@ +Fix crash on logout from too much wrong pin codes From 7db922e8ad253a66954ae710d156b8e5fa7f1bdf Mon Sep 17 00:00:00 2001 From: aringenbach Date: Thu, 15 Sep 2022 10:03:04 +0200 Subject: [PATCH 011/104] Update changelog to original issue --- changelog.d/6705.bugfix | 1 + changelog.d/6724.bugfix | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 changelog.d/6705.bugfix delete mode 100644 changelog.d/6724.bugfix diff --git a/changelog.d/6705.bugfix b/changelog.d/6705.bugfix new file mode 100644 index 000000000..9fd360053 --- /dev/null +++ b/changelog.d/6705.bugfix @@ -0,0 +1 @@ +Fix crash on logout when syncing is currently in progress diff --git a/changelog.d/6724.bugfix b/changelog.d/6724.bugfix deleted file mode 100644 index f1aa01659..000000000 --- a/changelog.d/6724.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix crash on logout from too much wrong pin codes From 3b9da1566ca1a5f0162d2ef841cd3aefdd8a10c4 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Thu, 15 Sep 2022 16:38:06 +0100 Subject: [PATCH 012/104] Disable unnecessary network breadcrumbs in Sentry --- Riot/Modules/Analytics/SentryMonitoringClient.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Riot/Modules/Analytics/SentryMonitoringClient.swift b/Riot/Modules/Analytics/SentryMonitoringClient.swift index e296c0d5d..d15cf8039 100644 --- a/Riot/Modules/Analytics/SentryMonitoringClient.swift +++ b/Riot/Modules/Analytics/SentryMonitoringClient.swift @@ -37,6 +37,10 @@ struct SentryMonitoringClient { options.sampleRate = 0.1 options.tracesSampleRate = 0.1 + // Disable unnecessary network tracking + options.enableNetworkBreadcrumbs = false + options.enableNetworkTracking = false + options.beforeSend = { event in MXLog.debug("[SentryMonitoringClient] Issue detected: \(event)") return event From 65260bd08248d29d5b82546e577308e5ad3e3d35 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Thu, 15 Sep 2022 16:39:38 +0100 Subject: [PATCH 013/104] Changelog --- changelog.d/pr-6726.change | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/pr-6726.change diff --git a/changelog.d/pr-6726.change b/changelog.d/pr-6726.change new file mode 100644 index 000000000..5c1dfa81d --- /dev/null +++ b/changelog.d/pr-6726.change @@ -0,0 +1 @@ +Sentry: Disable unnecessary network breadcrumbs From 60832e1ea2189b53c7b8b6ee2b31170f2b330fcb Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Thu, 15 Sep 2022 20:20:26 +0200 Subject: [PATCH 014/104] Pass country code into MSISDN requests Fixes #6723 --- Riot/Modules/Settings/SettingsViewController.m | 4 +++- changelog.d/6723.bugfix | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 changelog.d/6723.bugfix diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 6b9a19f97..9b69c0c19 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -3702,11 +3702,13 @@ ChangePasswordCoordinatorBridgePresenterDelegate> msisdn = [NSString stringWithFormat:@"+%@", [e164 substringFromIndex:2]]; } + NSString *countryCode = newPhoneNumberCell.isoCountryCode; + [self showAuthenticationIfNeededForAdding:kMX3PIDMediumMSISDN withSession:session completion:^(NSDictionary *authParams) { [self startActivityIndicator]; __block MX3PidAddSession *new3Pid; - new3Pid = [session.threePidAddManager startAddPhoneNumberSessionWithPhoneNumber:msisdn countryCode:nil success:^{ + new3Pid = [session.threePidAddManager startAddPhoneNumberSessionWithPhoneNumber:msisdn countryCode:countryCode success:^{ [self showValidationMsisdnDialogWithMessage:[VectorL10n accountMsisdnValidationMessage] for3PidAddSession:new3Pid threePidAddManager:session.threePidAddManager authenticationParameters:authParams]; diff --git a/changelog.d/6723.bugfix b/changelog.d/6723.bugfix new file mode 100644 index 000000000..228ff2f47 --- /dev/null +++ b/changelog.d/6723.bugfix @@ -0,0 +1 @@ +Pass country code into MSISDN requests From cc737fe1ccbf41f28ac84f38cfbfec0c67f4dbe3 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Thu, 15 Sep 2022 21:20:47 +0200 Subject: [PATCH 015/104] Fix login crash on Xcode 14 builds Fixes #6722 --- .../Authentication/Common/Service/MatrixSDK/LoginWizard.swift | 2 +- changelog.d/6722.bugfix | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/6722.bugfix diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginWizard.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginWizard.swift index f56319f37..3c9cf0730 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginWizard.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginWizard.swift @@ -51,7 +51,7 @@ class LoginWizard { /// - deviceID: The device ID, optional. If not provided or nil, the server will generate one. /// - removeOtherAccounts: If set to true, existing accounts with different user identifiers will be removed. /// - Returns: An `MXSession` if the login is successful. - func login(login: String, + @MainActor func login(login: String, password: String, initialDeviceName: String, deviceID: String? = nil, diff --git a/changelog.d/6722.bugfix b/changelog.d/6722.bugfix new file mode 100644 index 000000000..9a7b6dd8f --- /dev/null +++ b/changelog.d/6722.bugfix @@ -0,0 +1 @@ +Fix login crash on Xcode 14 builds From 3acfdbc83d85bd6ccafcd222a63779eb5087e01b Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Thu, 15 Sep 2022 09:46:23 +0300 Subject: [PATCH 016/104] User session flow coordinator and details view --- .../MockUserSessionDetailsScreenState.swift | 57 ++++++++++++ .../UserSessionDetailsItemView.swift | 76 ++++++++++++++++ .../UserSessionDetailsModels.swift | 40 +++++++++ .../DetailsView/UserSessionDetailsView.swift | 71 +++++++++++++++ .../UserSessionDetailsViewModel.swift | 72 +++++++++++++++ .../UserSessionFlowCoordinator.swift | 89 +++++++++++++++++++ .../UserSessionsFlowCoordinator.swift | 12 +++ .../UserSessionsOverviewCoordinator.swift | 18 +++- .../UserSessionsOverviewService.swift | 8 +- .../View/UserSessionsOverview.swift | 2 +- 10 files changed, 439 insertions(+), 6 deletions(-) create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/MockUserSessionDetailsScreenState.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsItemView.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsModels.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsView.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSession/UserSessionFlowCoordinator.swift diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/MockUserSessionDetailsScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/MockUserSessionDetailsScreenState.swift new file mode 100644 index 000000000..d055cb60d --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/MockUserSessionDetailsScreenState.swift @@ -0,0 +1,57 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +/// Using an enum for the screen allows you define the different state cases with +/// the relevant associated data for each case. +enum MockUserSessionDetailsScreenState: MockScreenState, CaseIterable { + // A case for each state you want to represent + // with specific, minimal associated data that will allow you + // mock that screen. + case allSections + case sessionSectionOnly + + /// The associated screen + var screenType: Any.Type { + UserSessionDetailsView.self + } + + /// A list of screen state definitions + static var allCases: [MockUserSessionDetailsScreenState] { + // Each of the presence statuses + return [.allSections, sessionSectionOnly] + } + + /// Generate the view struct for the screen state. + var screenView: ([Any], AnyView) { + let currentSessionInfo: UserSessionInfo + switch self { + case .allSections: + currentSessionInfo = UserSessionInfo(sessionId: "session", sessionName: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + case .sessionSectionOnly: + currentSessionInfo = UserSessionInfo(sessionId: "session", sessionName: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: nil, lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + } + let viewModel = UserSessionDetailsViewModel(userSessionInfo: currentSessionInfo) + + // can simulate service and viewModel actions here if needs be. + + return ( + [currentSessionInfo], + AnyView(UserSessionDetailsView(viewModel: viewModel.context)) + ) + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsItemView.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsItemView.swift new file mode 100644 index 000000000..f00a77aba --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsItemView.swift @@ -0,0 +1,76 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct UserSessionDetailsItemView: View { + + // MARK: - Properties + + // MARK: Private + + @Environment(\.theme) private var theme: ThemeSwiftUI + + // MARK: Public + + let viewData: UserSessionDetailsSectionItemViewData + + var body: some View { + HStack() { + Text(viewData.title) + .font(theme.fonts.subheadline) + .foregroundColor(theme.colors.secondaryContent) + .frame(maxWidth: .infinity, alignment: .leading) + .frame(maxHeight: .infinity, alignment: .top) + Text(viewData.value) + .font(theme.fonts.subheadline) + .foregroundColor(theme.colors.primaryContent) + .multilineTextAlignment(.trailing) + } + .contextMenu { + Button { + UIPasteboard.general.string = viewData.value + } label: { + Label("Copy", systemImage: "doc.on.doc") + } + } + .padding([.leading, .trailing], 20) + .padding([.top, .bottom], 12) + } +} + +// MARK: - Previews + +struct UserSessionDetailsItemView_Previews: PreviewProvider { + static var previews: some View { + Group { + List { + UserSessionDetailsItemView(viewData: UserSessionDetailsSectionItemViewData(title: "Session name", value: "123")) + .theme(.light) + .preferredColorScheme(.light) + .listRowInsets(EdgeInsets()) + } + .listStyle(.grouped) + List { + UserSessionDetailsItemView(viewData: UserSessionDetailsSectionItemViewData(title: "Session name", value: "123")) + .theme(.dark) + .preferredColorScheme(.dark) + .listRowInsets(EdgeInsets()) + } + .listStyle(.grouped) + } + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsModels.swift new file mode 100644 index 000000000..f5f188d0d --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsModels.swift @@ -0,0 +1,40 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +enum UserSessionDetailsViewModelResult { +} + +enum UserSessionDetailsViewAction { +} + +struct UserSessionDetailsViewState: BindableState { + let sections: [UserSessionDetailsSectionViewData] +} + +struct UserSessionDetailsSectionViewData: Identifiable { + let id = UUID() + let header: String + let footer: String? + let items: [UserSessionDetailsSectionItemViewData] +} + +struct UserSessionDetailsSectionItemViewData: Identifiable { + let id = UUID() + let title: String + let value: String +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsView.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsView.swift new file mode 100644 index 000000000..7099b48b8 --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsView.swift @@ -0,0 +1,71 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct UserSessionDetailsView: View { + + // MARK: - Properties + + // MARK: Private + + @Environment(\.theme) private var theme: ThemeSwiftUI + + // MARK: Public + + @ObservedObject var viewModel: UserSessionDetailsViewModel.Context + + var body: some View { + List { + ForEach(viewModel.viewState.sections) { section in + SwiftUI.Section { + ForEach(section.items) { item in + UserSessionDetailsItemView(viewData: item) + .listRowInsets(EdgeInsets()) + } + } header: { + Text(section.header) + .foregroundColor(theme.colors.secondaryContent) + .font(theme.fonts.footnote) + .padding([.leading, .trailing], 20) + .padding([.top, .bottom], 8) + } footer: { + if let footer = section.footer { + Text(footer) + .foregroundColor(theme.colors.secondaryContent) + .font(theme.fonts.footnote) + .padding([.leading, .trailing], 20) + .padding(.top, 8) + .padding(.bottom, 32) + } + } + .listRowInsets(EdgeInsets()) + } + } + .listStyle(.grouped) + .navigationBarTitle("Session details", displayMode: .inline) + } +} + +// MARK: - Previews + +struct UserSessionDetails_Previews: PreviewProvider { + static let stateRenderer = MockUserSessionDetailsScreenState.stateRenderer + static var previews: some View { + stateRenderer.screenGroup(addNavigation: true).theme(.light).preferredColorScheme(.light) + stateRenderer.screenGroup(addNavigation: true).theme(.dark).preferredColorScheme(.dark) + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift new file mode 100644 index 000000000..463da88a5 --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift @@ -0,0 +1,72 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +typealias UserSessionDetailsViewModelType = StateStoreViewModel + +class UserSessionDetailsViewModel: UserSessionDetailsViewModelType { + + // MARK: - Properties + + // MARK: Private + + // MARK: Public + + var completion: ((UserSessionDetailsViewModelResult) -> Void)? + + // MARK: - Setup + + init(userSessionInfo: UserSessionInfo) { + var sections = [UserSessionDetailsSectionViewData]() + + var sessionItems = [UserSessionDetailsSectionItemViewData]() + if let sessionName = userSessionInfo.sessionName { + sessionItems.append(UserSessionDetailsSectionItemViewData(title: "Session name", + value: sessionName)) + } + sessionItems.append(UserSessionDetailsSectionItemViewData(title: "Session ID", + value: userSessionInfo.sessionId)) + sections.append(UserSessionDetailsSectionViewData(header: "SESSION", + footer: "Copy any data by tapping on it and holding it down.", + items: sessionItems)) + + var deviceSectionItems = [UserSessionDetailsSectionItemViewData]() + if let lastSeenIP = userSessionInfo.lastSeenIP { + deviceSectionItems.append(UserSessionDetailsSectionItemViewData(title: "IP address", + value: lastSeenIP)) + } + if deviceSectionItems.count > 0 { + sections.append(UserSessionDetailsSectionViewData(header: "DEVICE", + footer: nil, + items: deviceSectionItems)) + } + + let initialViewState = UserSessionDetailsViewState(sections: sections) + super.init(initialViewState: initialViewState) + } + + // MARK: - Public + + override func process(viewAction: UserSessionDetailsViewAction) { + + } + + // MARK: - Private +} + diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/UserSessionFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/UserSessionFlowCoordinator.swift new file mode 100644 index 000000000..bb28cbb40 --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/UserSessionFlowCoordinator.swift @@ -0,0 +1,89 @@ +// File created from FlowTemplate +// $ createRootCoordinator.sh Folder UserSessionFlow UserSessionOverview +/* + Copyright 2021 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import CommonKit +import UIKit + +protocol UserSessionFlowCoordinatorDelegate: AnyObject { +// func userSessionFlowCoordinatorDidComplete(_ coordinator: UserSessionFlowCoordinatorProtocol) +// +// /// Called when the view has been dismissed by gesture when presented modally (not in full screen). +// func userSessionFlowCoordinatorDidDismissInteractively(_ coordinator: UserSessionFlowCoordinatorProtocol) +} + +struct UserSessionFlowCoordinatorParameters { + let session: MXSession + let navigationRouter: NavigationRouterType + let userSessionInfo: UserSessionInfo +} + +final class UserSessionFlowCoordinator: Coordinator, Presentable { + + // MARK: - Properties + + // MARK: Private + + private let parameters: UserSessionFlowCoordinatorParameters + private let userSessionDetailsViewHostingController: UIViewController + private var userSessionDetailsViewModel: UserSessionDetailsViewModel + + private var navigationRouter: NavigationRouterType { + return self.parameters.navigationRouter + } + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: UserSessionFlowCoordinatorDelegate? + + // MARK: - Setup + + init(parameters: UserSessionFlowCoordinatorParameters) { + self.parameters = parameters + + // todo: builder + let viewModel = UserSessionDetailsViewModel(userSessionInfo: parameters.userSessionInfo) + let view = UserSessionDetailsView(viewModel: viewModel.context) + userSessionDetailsViewModel = viewModel + userSessionDetailsViewHostingController = VectorHostingController(rootView: view) + } + + // MARK: - Public + + func start() { + // TODO: change to showUserSessionOverview() + showUserSessionDetails() + } + + private func showUserSessionOverview() { + // TODO: PSG-690 + } + + private func showUserSessionDetails() { + self.navigationRouter.push(self.userSessionDetailsViewHostingController, animated: true, popCompletion: nil) + } + + func toPresentable() -> UIViewController { + return self.navigationRouter.toPresentable() + } + + // MARK: - Private + +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift index 42151e7cf..7347294ee 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift @@ -52,6 +52,7 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { let rootCoordinatorParameters = UserSessionsOverviewCoordinatorParameters(session: self.parameters.session) let rootCoordinator = UserSessionsOverviewCoordinator(parameters: rootCoordinatorParameters) + rootCoordinator.delegate = self rootCoordinator.start() @@ -74,3 +75,14 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { return self.navigationRouter.toPresentable() } } + +extension UserSessionsFlowCoordinator: UserSessionsOverviewCoordinatorDelegate { + func showUserSessionOverview(session: UserSessionInfo) { + let parameters = UserSessionFlowCoordinatorParameters(session: parameters.session, + navigationRouter: navigationRouter, + userSessionInfo: session) + let coordinator = UserSessionFlowCoordinator(parameters: parameters) + coordinator.start() + self.add(childCoordinator: coordinator) + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift index 1494eb7c2..e36a6e240 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift @@ -21,6 +21,10 @@ struct UserSessionsOverviewCoordinatorParameters { let session: MXSession } +protocol UserSessionsOverviewCoordinatorDelegate: AnyObject { + func showUserSessionOverview(session: UserSessionInfo) +} + final class UserSessionsOverviewCoordinator: Coordinator, Presentable { // MARK: - Properties @@ -30,7 +34,8 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { private let parameters: UserSessionsOverviewCoordinatorParameters private let userSessionsOverviewHostingController: UIViewController private var userSessionsOverviewViewModel: UserSessionsOverviewViewModelProtocol - + private let service: UserSessionsOverviewService + private var indicatorPresenter: UserIndicatorTypePresenterProtocol private var loadingIndicator: UserIndicator? @@ -40,11 +45,15 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { var childCoordinators: [Coordinator] = [] var completion: (() -> Void)? + weak var delegate: UserSessionsOverviewCoordinatorDelegate? + // MARK: - Setup init(parameters: UserSessionsOverviewCoordinatorParameters) { self.parameters = parameters - let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: UserSessionsOverviewService(mxSession: parameters.session)) + let service = UserSessionsOverviewService(mxSession: parameters.session) + self.service = service + let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service) let view = UserSessionsOverview(viewModel: viewModel.context) userSessionsOverviewViewModel = viewModel @@ -117,7 +126,10 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { } private func showUserSessionDetails(sessionId: String) { - // TODO + guard let sessionInfo = service.getOtherSession(sessionId: sessionId) else { + return + } + delegate?.showUserSessionOverview(session: sessionInfo) } private func showAllOtherSessions() { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift index 59ccf1e02..b9efa16e0 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift @@ -47,14 +47,18 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { self.mxSession.matrixRestClient.devices { response in switch response { case .success(let devices): - let overviewData = self.userSessionsOverviewData(from: devices) - completion(.success(overviewData)) + self.lastOverviewData = self.userSessionsOverviewData(from: devices) + completion(.success(self.lastOverviewData)) case .failure(let error): completion(.failure(error)) } } } + func getOtherSession(sessionId: String) -> UserSessionInfo? { + lastOverviewData.otherSessionsInfo.first(where: {$0.sessionId == sessionId}) + } + // MARK: - Private private func setupInitialOverviewData() { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift index fbfb70c3f..27601502f 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift @@ -75,7 +75,7 @@ struct UserSessionsOverview: View { viewModel.send(viewAction: .viewAppeared) } } - + private var otherSessionsSection: some View { SwiftUI.Section { From aa31c852f1287a8eac23a874452dea2691dcac08 Mon Sep 17 00:00:00 2001 From: Doug Date: Fri, 16 Sep 2022 12:31:25 +0100 Subject: [PATCH 017/104] Put the session creator on the main actor instead. This covers all login and registration options. --- .../Common/Service/MatrixSDK/LoginWizard.swift | 14 +++++++------- .../Service/MatrixSDK/RegistrationWizard.swift | 2 +- .../Common/Service/MatrixSDK/SessionCreator.swift | 2 ++ .../Authentication/Mocks/MockSessionCreator.swift | 1 + RiotTests/SessionCreatorTests.swift | 4 ++-- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginWizard.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginWizard.swift index 3c9cf0730..ae759b058 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginWizard.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginWizard.swift @@ -51,7 +51,7 @@ class LoginWizard { /// - deviceID: The device ID, optional. If not provided or nil, the server will generate one. /// - removeOtherAccounts: If set to true, existing accounts with different user identifiers will be removed. /// - Returns: An `MXSession` if the login is successful. - @MainActor func login(login: String, + func login(login: String, password: String, initialDeviceName: String, deviceID: String? = nil, @@ -78,9 +78,9 @@ class LoginWizard { } let credentials = try await client.login(parameters: parameters) - return sessionCreator.createSession(credentials: credentials, - client: client, - removeOtherAccounts: removeOtherAccounts) + return await sessionCreator.createSession(credentials: credentials, + client: client, + removeOtherAccounts: removeOtherAccounts) } /// Exchange a login token to an access token. @@ -91,9 +91,9 @@ class LoginWizard { func login(with token: String, removeOtherAccounts: Bool = false) async throws -> MXSession { let parameters = LoginTokenParameters(token: token) let credentials = try await client.login(parameters: parameters) - return sessionCreator.createSession(credentials: credentials, - client: client, - removeOtherAccounts: removeOtherAccounts) + return await sessionCreator.createSession(credentials: credentials, + client: client, + removeOtherAccounts: removeOtherAccounts) } /// Ask the homeserver to reset the user password. The password will not be diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationWizard.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationWizard.swift index 9f05a582b..2a2a0989b 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationWizard.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationWizard.swift @@ -255,7 +255,7 @@ class RegistrationWizard { do { let response = try await client.register(parameters: parameters) let credentials = MXCredentials(loginResponse: response, andDefaultCredentials: client.credentials) - return .success(sessionCreator.createSession(credentials: credentials, client: client, removeOtherAccounts: false)) + return await .success(sessionCreator.createSession(credentials: credentials, client: client, removeOtherAccounts: false)) } catch { let nsError = error as NSError diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/SessionCreator.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/SessionCreator.swift index c51f2bb61..955f13fed 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/SessionCreator.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/SessionCreator.swift @@ -23,6 +23,7 @@ protocol SessionCreatorProtocol { /// - client: The client that completed the authentication. /// - removeOtherAccounts: Flag to remove other accounts than the account specified with the `credentials.userId`. /// - Returns: A new `MXSession` for the account. + @MainActor func createSession(credentials: MXCredentials, client: AuthenticationRestClient, removeOtherAccounts: Bool) -> MXSession } @@ -35,6 +36,7 @@ struct SessionCreator: SessionCreatorProtocol { self.accountManager = accountManager } + @MainActor func createSession(credentials: MXCredentials, client: AuthenticationRestClient, removeOtherAccounts: Bool) -> MXSession { // Use identity server provided in the client if credentials.identityServer == nil { diff --git a/RiotTests/Modules/Authentication/Mocks/MockSessionCreator.swift b/RiotTests/Modules/Authentication/Mocks/MockSessionCreator.swift index e62578d37..2b018dae2 100644 --- a/RiotTests/Modules/Authentication/Mocks/MockSessionCreator.swift +++ b/RiotTests/Modules/Authentication/Mocks/MockSessionCreator.swift @@ -20,6 +20,7 @@ import Foundation struct MockSessionCreator: SessionCreatorProtocol { /// Returns a basic session created from the supplied credentials. This prevents the app from setting up the account during tests. + @MainActor func createSession(credentials: MXCredentials, client: AuthenticationRestClient, removeOtherAccounts: Bool) -> MXSession { let client = MXRestClient(credentials: credentials, unauthenticatedHandler: { _,_,_,_ in }) // The handler is expected if credentials are set. diff --git a/RiotTests/SessionCreatorTests.swift b/RiotTests/SessionCreatorTests.swift index f2003545e..dbc28c728 100644 --- a/RiotTests/SessionCreatorTests.swift +++ b/RiotTests/SessionCreatorTests.swift @@ -19,7 +19,7 @@ import XCTest class SessionCreatorTests: XCTestCase { - func testIdentityServer() throws { + func testIdentityServer() async throws { let sessionCreator = SessionCreator(withAccountManager: .mock) let mockIS = "mock_identity_server" @@ -29,7 +29,7 @@ class SessionCreatorTests: XCTestCase { accessToken: "mock_access_token") let client = MXRestClient(credentials: credentials) client.identityServer = mockIS - let session = sessionCreator.createSession(credentials: credentials, client: client, removeOtherAccounts: false) + let session = await sessionCreator.createSession(credentials: credentials, client: client, removeOtherAccounts: false) XCTAssertEqual(credentials.identityServer, mockIS) XCTAssertEqual(session.credentials.identityServer, mockIS) From 8589f7bca16c482d81c193a713220224daec22af Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Fri, 16 Sep 2022 14:34:00 +0300 Subject: [PATCH 018/104] Added layout constants --- .../UserSessionDetailsItemView.swift | 32 +++++++++++++------ .../DetailsView/UserSessionDetailsView.swift | 21 +++++++----- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsItemView.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsItemView.swift index f00a77aba..3a4bb00ed 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsItemView.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsItemView.swift @@ -27,6 +27,12 @@ struct UserSessionDetailsItemView: View { // MARK: Public let viewData: UserSessionDetailsSectionItemViewData + let horizontalPadding: CGFloat + + init(viewData: UserSessionDetailsSectionItemViewData, horizontalPadding: CGFloat = 20) { + self.viewData = viewData + self.horizontalPadding = horizontalPadding + } var body: some View { HStack() { @@ -47,7 +53,7 @@ struct UserSessionDetailsItemView: View { Label("Copy", systemImage: "doc.on.doc") } } - .padding([.leading, .trailing], 20) + .padding([.leading, .trailing], horizontalPadding) .padding([.top, .bottom], 12) } } @@ -58,18 +64,26 @@ struct UserSessionDetailsItemView_Previews: PreviewProvider { static var previews: some View { Group { List { - UserSessionDetailsItemView(viewData: UserSessionDetailsSectionItemViewData(title: "Session name", value: "123")) - .theme(.light) - .preferredColorScheme(.light) - .listRowInsets(EdgeInsets()) + UserSessionDetailsItemView(viewData: UserSessionDetailsSectionItemViewData(title: "Session name", + value: "Element Web: Firefox on macOS")) + .listRowInsets(EdgeInsets()) + UserSessionDetailsItemView(viewData: UserSessionDetailsSectionItemViewData(title: "Session ID", + value: "76c95352559d-react-7c57680b93db-js-b64dbdce74b0")) + .listRowInsets(EdgeInsets()) } + .preferredColorScheme(.light) + .listStyle(.grouped) List { - UserSessionDetailsItemView(viewData: UserSessionDetailsSectionItemViewData(title: "Session name", value: "123")) - .theme(.dark) - .preferredColorScheme(.dark) - .listRowInsets(EdgeInsets()) + UserSessionDetailsItemView(viewData: UserSessionDetailsSectionItemViewData(title: "Session name", + value: "Element Web: Firefox on macOS")) + .listRowInsets(EdgeInsets()) + UserSessionDetailsItemView(viewData: UserSessionDetailsSectionItemViewData(title: "Session ID", + value: "76c95352559d-react-7c57680b93db-js-b64dbdce74b0")) + .listRowInsets(EdgeInsets()) } + .preferredColorScheme(.dark) + .theme(.dark) .listStyle(.grouped) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsView.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsView.swift index 7099b48b8..94d70a40a 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsView.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsView.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,11 @@ struct UserSessionDetailsView: View { // MARK: Private + private enum LayoutConstants { + static let listItemHorizontalPadding: CGFloat = 20 + static let sectionVerticalPadding: CGFloat = 8 + } + @Environment(\.theme) private var theme: ThemeSwiftUI // MARK: Public @@ -33,30 +38,30 @@ struct UserSessionDetailsView: View { ForEach(viewModel.viewState.sections) { section in SwiftUI.Section { ForEach(section.items) { item in - UserSessionDetailsItemView(viewData: item) + UserSessionDetailsItemView(viewData: item, horizontalPadding: LayoutConstants.listItemHorizontalPadding) .listRowInsets(EdgeInsets()) } } header: { Text(section.header) .foregroundColor(theme.colors.secondaryContent) .font(theme.fonts.footnote) - .padding([.leading, .trailing], 20) - .padding([.top, .bottom], 8) + .padding([.leading, .trailing], LayoutConstants.listItemHorizontalPadding) + .padding(.top, 32) + .padding(.bottom, LayoutConstants.sectionVerticalPadding) } footer: { if let footer = section.footer { Text(footer) .foregroundColor(theme.colors.secondaryContent) .font(theme.fonts.footnote) - .padding([.leading, .trailing], 20) - .padding(.top, 8) - .padding(.bottom, 32) + .padding([.leading, .trailing], LayoutConstants.listItemHorizontalPadding) + .padding(.top, LayoutConstants.sectionVerticalPadding) } } .listRowInsets(EdgeInsets()) } } .listStyle(.grouped) - .navigationBarTitle("Session details", displayMode: .inline) + .navigationBarTitle("Session details") } } From 164051a9f24d0c72b18a8ac9f161b5cf11d85bca Mon Sep 17 00:00:00 2001 From: Doug Date: Fri, 16 Sep 2022 19:46:11 +0100 Subject: [PATCH 019/104] Only disable the composer if the room is yet to be created. --- Riot/Generated/Strings.swift | 2 +- Riot/Modules/Room/RoomViewController.m | 8 ++++---- changelog.d/6708.bugfix | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 changelog.d/6708.bugfix diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index bdad88cd7..94a7f4631 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -187,7 +187,7 @@ public class VectorL10n: NSObject { public static var allChatsEmptySpaceInformation: String { return VectorL10n.tr("Vector", "all_chats_empty_space_information") } - /// This is where you're unread messages will show up, when you have some. + /// This is where your unread messages will show up, when you have some. public static var allChatsEmptyUnreadsPlaceholderMessage: String { return VectorL10n.tr("Vector", "all_chats_empty_unreads_placeholder_message") } diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 771c3d657..cb0fb1a31 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -1544,10 +1544,6 @@ static CGSize kThreadListBarButtonItemImageSize; */ - (void)createDiscussionIfNeeded:(void (^)(BOOL readyToSend))onComplete { - // Disable the input tool bar during this operation. This prevents us from creating several discussions, or - // trying to send several invites. - self.inputToolbarView.userInteractionEnabled = false; - void(^completion)(BOOL) = ^(BOOL readyToSend) { self.inputToolbarView.userInteractionEnabled = true; if (onComplete) { @@ -1557,6 +1553,10 @@ static CGSize kThreadListBarButtonItemImageSize; if (self.directChatTargetUser) { + // Disable the input tool bar during this operation. This prevents us from creating several discussions, or + // trying to send several invites. + self.inputToolbarView.userInteractionEnabled = false; + [self createDiscussionWithUser:self.directChatTargetUser completion:completion]; } else diff --git a/changelog.d/6708.bugfix b/changelog.d/6708.bugfix new file mode 100644 index 000000000..10961f90f --- /dev/null +++ b/changelog.d/6708.bugfix @@ -0,0 +1 @@ +Message Composer: Stop the keyboard jumping after sending a message on certain devices. From 8d12ae547587673046d2296d13af7fbe2e6eb595 Mon Sep 17 00:00:00 2001 From: Doug Date: Fri, 16 Sep 2022 21:50:01 +0100 Subject: [PATCH 020/104] Add missing source views for iPad. --- Riot/Modules/Home/AllChats/AllChatsCoordinator.swift | 5 +++-- changelog.d/6654.bugfix | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 changelog.d/6654.bugfix diff --git a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift index 803ef5b71..27af3a94b 100644 --- a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift +++ b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift @@ -344,7 +344,8 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { var subMenuActions: [UIAction] = [] if BuildSettings.sideMenuShowInviteFriends { subMenuActions.append(UIAction(title: VectorL10n.sideMenuActionInviteFriends, image: UIImage(systemName: "square.and.arrow.up.fill")) { [weak self] action in - self?.showInviteFriends(from: nil) + guard let self = self else { return } + self.showInviteFriends(from: self.avatarMenuButton) }) } @@ -585,7 +586,7 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { signOutAlertPresenter.present(for: keyBackup.state, areThereKeysToBackup: keyBackup.hasKeysToBackup, from: self.allChatsViewController, - sourceView: nil, + sourceView: avatarMenuButton, animated: true) } diff --git a/changelog.d/6654.bugfix b/changelog.d/6654.bugfix new file mode 100644 index 000000000..1b342f283 --- /dev/null +++ b/changelog.d/6654.bugfix @@ -0,0 +1 @@ +Fix crash presenting Sign Out or Invite to Element menu items on iPad. From e4450e90e3f84edeb6c75a026a20196168ec8aff Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Mon, 19 Sep 2022 10:26:42 +0300 Subject: [PATCH 021/104] Added translation strings --- Riot/Assets/en.lproj/Vector.strings | 7 +++++ Riot/Generated/Strings.swift | 28 +++++++++++++++++++ .../UserSessionDetailsItemView.swift | 2 +- .../DetailsView/UserSessionDetailsView.swift | 2 +- .../UserSessionDetailsViewModel.swift | 14 +++++----- 5 files changed, 44 insertions(+), 9 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 1a55df6d0..dedbeb99e 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2384,6 +2384,13 @@ To enable access, tap Settings> Location and select Always"; "device_name_mobile" = "%@ Mobile"; "device_name_unknown" = "Unknown client"; +"user_session_details_title" = "Session details"; +"user_session_details_session_section_header" = "SESSION"; +"user_session_details_device_section_header" = "DEVICE"; +"user_session_details_session_name" = "Session name"; +"user_session_details_session_id" = "Session ID"; +"user_session_details_session_section_footer" = "Copy any data by tapping on it and holding it down."; +"user_session_details_device_ip_address" = "IP address"; // MARK: - MatrixKit diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index bdad88cd7..f4de1f40b 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -8467,6 +8467,34 @@ public class VectorL10n: NSObject { public static var userIdTitle: String { return VectorL10n.tr("Vector", "user_id_title") } + /// IP address + public static var userSessionDetailsDeviceIpAddress: String { + return VectorL10n.tr("Vector", "user_session_details_device_ip_address") + } + /// DEVICE + public static var userSessionDetailsDeviceSectionHeader: String { + return VectorL10n.tr("Vector", "user_session_details_device_section_header") + } + /// Session ID + public static var userSessionDetailsSessionId: String { + return VectorL10n.tr("Vector", "user_session_details_session_id") + } + /// Session name + public static var userSessionDetailsSessionName: String { + return VectorL10n.tr("Vector", "user_session_details_session_name") + } + /// Copy any data by tapping on it and holding it down. + public static var userSessionDetailsSessionSectionFooter: String { + return VectorL10n.tr("Vector", "user_session_details_session_section_footer") + } + /// SESSION + public static var userSessionDetailsSessionSectionHeader: String { + return VectorL10n.tr("Vector", "user_session_details_session_section_header") + } + /// Session details + public static var userSessionDetailsTitle: String { + return VectorL10n.tr("Vector", "user_session_details_title") + } /// %@ · Last activity %@ public static func userSessionItemDetails(_ p1: String, _ p2: String) -> String { return VectorL10n.tr("Vector", "user_session_item_details", p1, p2) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsItemView.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsItemView.swift index 3a4bb00ed..ca9d4b9e0 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsItemView.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsItemView.swift @@ -50,7 +50,7 @@ struct UserSessionDetailsItemView: View { Button { UIPasteboard.general.string = viewData.value } label: { - Label("Copy", systemImage: "doc.on.doc") + Label(VectorL10n.copyButtonName, systemImage: "doc.on.doc") } } .padding([.leading, .trailing], horizontalPadding) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsView.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsView.swift index 94d70a40a..1e851f377 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsView.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsView.swift @@ -61,7 +61,7 @@ struct UserSessionDetailsView: View { } } .listStyle(.grouped) - .navigationBarTitle("Session details") + .navigationBarTitle(VectorL10n.userSessionDetailsTitle) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift index 463da88a5..1ba953067 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift @@ -37,26 +37,26 @@ class UserSessionDetailsViewModel: UserSessionDetailsViewModelType { var sessionItems = [UserSessionDetailsSectionItemViewData]() if let sessionName = userSessionInfo.sessionName { - sessionItems.append(UserSessionDetailsSectionItemViewData(title: "Session name", + sessionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.userSessionDetailsSessionName, value: sessionName)) } - sessionItems.append(UserSessionDetailsSectionItemViewData(title: "Session ID", + sessionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.keyVerificationManuallyVerifyDeviceIdTitle, value: userSessionInfo.sessionId)) - sections.append(UserSessionDetailsSectionViewData(header: "SESSION", - footer: "Copy any data by tapping on it and holding it down.", + sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader, + footer: VectorL10n.userSessionDetailsSessionSectionFooter, items: sessionItems)) var deviceSectionItems = [UserSessionDetailsSectionItemViewData]() if let lastSeenIP = userSessionInfo.lastSeenIP { - deviceSectionItems.append(UserSessionDetailsSectionItemViewData(title: "IP address", + deviceSectionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.userSessionDetailsDeviceIpAddress, value: lastSeenIP)) } if deviceSectionItems.count > 0 { - sections.append(UserSessionDetailsSectionViewData(header: "DEVICE", + sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsDeviceSectionHeader, footer: nil, items: deviceSectionItems)) } - + let initialViewState = UserSessionDetailsViewState(sections: sections) super.init(initialViewState: initialViewState) } From 730b6ee3b6149eb552ee3a328011d8878fc9af42 Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Mon, 19 Sep 2022 13:38:36 +0200 Subject: [PATCH 022/104] New App Layout: fixed rooms list doesn't update after log out and log into another account --- Riot/Modules/Home/AllChats/AllChatsViewController.swift | 5 +++++ changelog.d/6739.bugfix | 1 + 2 files changed, 6 insertions(+) create mode 100644 changelog.d/6739.bugfix diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index 7d33aa1fc..4da58903a 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -224,6 +224,11 @@ class AllChatsViewController: HomeViewController { guard let dataSource = dataSource else { return } dataSource.removeMatrixSession(mxSession) + + if dataSource.mxSessions.isEmpty { + // The user logged out -> we need to reset the data source + displayList(nil) + } } private func initDataSource() { diff --git a/changelog.d/6739.bugfix b/changelog.d/6739.bugfix new file mode 100644 index 000000000..0abdd05b5 --- /dev/null +++ b/changelog.d/6739.bugfix @@ -0,0 +1 @@ +New App Layout: fixed rooms list doesn't update after log out and log into another account From cb5ca3f0433c3e64d0b5d232a114e9156082c2d3 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Mon, 19 Sep 2022 16:05:28 +0300 Subject: [PATCH 023/104] Added unit tests --- .../UserSessionDetailsModels.swift | 20 +++- .../UserSessionDetailsViewModel.swift | 4 +- .../UserSessionFlowCoordinator.swift | 21 +--- .../UserSessionDetailsViewModelTests.swift | 112 ++++++++++++++++++ .../UserSessionFlowCoordinatorTests.swift | 77 ++++++++++++ 5 files changed, 214 insertions(+), 20 deletions(-) create mode 100644 RiotTests/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModelTests.swift create mode 100644 RiotTests/Modules/UserSessions/UserSession/UserSessionFlowCoordinatorTests.swift diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsModels.swift index f5f188d0d..c87f7c17f 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsModels.swift @@ -22,7 +22,8 @@ enum UserSessionDetailsViewModelResult { enum UserSessionDetailsViewAction { } -struct UserSessionDetailsViewState: BindableState { +struct UserSessionDetailsViewState: BindableState, Equatable { + let sections: [UserSessionDetailsSectionViewData] } @@ -38,3 +39,20 @@ struct UserSessionDetailsSectionItemViewData: Identifiable { let title: String let value: String } + +extension UserSessionDetailsSectionViewData: Equatable { + + static func == (lhs: UserSessionDetailsSectionViewData, rhs: UserSessionDetailsSectionViewData) -> Bool { + lhs.header == rhs.header && + lhs.footer == rhs.footer && + lhs.items == rhs.items + } +} + +extension UserSessionDetailsSectionItemViewData: Equatable { + + static func == (lhs: UserSessionDetailsSectionItemViewData, rhs: UserSessionDetailsSectionItemViewData) -> Bool { + lhs.title == rhs.title && + lhs.value == rhs.value + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift index 1ba953067..b8481ce3e 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift @@ -27,9 +27,7 @@ class UserSessionDetailsViewModel: UserSessionDetailsViewModelType { // MARK: Private // MARK: Public - - var completion: ((UserSessionDetailsViewModelResult) -> Void)? - + // MARK: - Setup init(userSessionInfo: UserSessionInfo) { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/UserSessionFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/UserSessionFlowCoordinator.swift index bb28cbb40..c5dd499dc 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/UserSessionFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/UserSessionFlowCoordinator.swift @@ -19,13 +19,6 @@ import CommonKit import UIKit -protocol UserSessionFlowCoordinatorDelegate: AnyObject { -// func userSessionFlowCoordinatorDidComplete(_ coordinator: UserSessionFlowCoordinatorProtocol) -// -// /// Called when the view has been dismissed by gesture when presented modally (not in full screen). -// func userSessionFlowCoordinatorDidDismissInteractively(_ coordinator: UserSessionFlowCoordinatorProtocol) -} - struct UserSessionFlowCoordinatorParameters { let session: MXSession let navigationRouter: NavigationRouterType @@ -37,40 +30,37 @@ final class UserSessionFlowCoordinator: Coordinator, Presentable { // MARK: - Properties // MARK: Private - + private let parameters: UserSessionFlowCoordinatorParameters private let userSessionDetailsViewHostingController: UIViewController private var userSessionDetailsViewModel: UserSessionDetailsViewModel - + private var navigationRouter: NavigationRouterType { return self.parameters.navigationRouter } // MARK: Public - + // Must be used only internally var childCoordinators: [Coordinator] = [] - weak var delegate: UserSessionFlowCoordinatorDelegate? - // MARK: - Setup init(parameters: UserSessionFlowCoordinatorParameters) { self.parameters = parameters - // todo: builder let viewModel = UserSessionDetailsViewModel(userSessionInfo: parameters.userSessionInfo) let view = UserSessionDetailsView(viewModel: viewModel.context) userSessionDetailsViewModel = viewModel userSessionDetailsViewHostingController = VectorHostingController(rootView: view) - } + } // MARK: - Public func start() { // TODO: change to showUserSessionOverview() showUserSessionDetails() - } + } private func showUserSessionOverview() { // TODO: PSG-690 @@ -85,5 +75,4 @@ final class UserSessionFlowCoordinator: Coordinator, Presentable { } // MARK: - Private - } diff --git a/RiotTests/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModelTests.swift b/RiotTests/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModelTests.swift new file mode 100644 index 000000000..ca932d340 --- /dev/null +++ b/RiotTests/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModelTests.swift @@ -0,0 +1,112 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +@testable import Element + +class UserSessionDetailsViewModelTests: XCTestCase { + + + func test_viewState_withSessionNameAndLastSeenIPNil_shouldReturnCorrectModel() { + let userSessionInfo = createUserSessionInfo(sessionId: "session", + sessionName: nil, + lastSeenIP: nil) + + var sessionItems = [UserSessionDetailsSectionItemViewData]() + sessionItems.append(sessionIdItem(sessionId: userSessionInfo.sessionId)) + + var sections = [UserSessionDetailsSectionViewData]() + sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader, + footer: VectorL10n.userSessionDetailsSessionSectionFooter, + items: sessionItems)) + let expectedModel = UserSessionDetailsViewState(sections: sections) + let sut = UserSessionDetailsViewModel(userSessionInfo: userSessionInfo) + + XCTAssertEqual(sut.state, expectedModel) + } + + func test_viewState_withSessionNameNotNilLastSeenIPNil_shouldReturnCorrectModel() { + let userSessionInfo = createUserSessionInfo(sessionId: "session", + sessionName: "session name", + lastSeenIP: nil) + + var sessionItems = [UserSessionDetailsSectionItemViewData]() + sessionItems.append(sessionNameItem(sessionName: "session name")) + sessionItems.append(sessionIdItem(sessionId: userSessionInfo.sessionId)) + + var sections = [UserSessionDetailsSectionViewData]() + sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader, + footer: VectorL10n.userSessionDetailsSessionSectionFooter, + items: sessionItems)) + + let expectedModel = UserSessionDetailsViewState(sections: sections) + let sut = UserSessionDetailsViewModel(userSessionInfo: userSessionInfo) + + XCTAssertEqual(sut.state, expectedModel) + } + + func test_viewState_withAllValues_shouldReturnCorrectModel() { + let userSessionInfo = createUserSessionInfo(sessionId: "session", + sessionName: "session name", + lastSeenIP: "0.0.0.0") + + var sessionItems = [UserSessionDetailsSectionItemViewData]() + sessionItems.append(sessionNameItem(sessionName: "session name")) + sessionItems.append(sessionIdItem(sessionId: userSessionInfo.sessionId)) + + var sections = [UserSessionDetailsSectionViewData]() + sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader, + footer: VectorL10n.userSessionDetailsSessionSectionFooter, + items: sessionItems)) + + var deviceSectionItems = [UserSessionDetailsSectionItemViewData]() + deviceSectionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.userSessionDetailsDeviceIpAddress, + value: "0.0.0.0")) + sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsDeviceSectionHeader, + footer: nil, + items: deviceSectionItems)) + + let expectedModel = UserSessionDetailsViewState(sections: sections) + let sut = UserSessionDetailsViewModel(userSessionInfo: userSessionInfo) + + XCTAssertEqual(sut.state, expectedModel) + } + + private func createUserSessionInfo(sessionId: String, + sessionName: String?, + deviceType: DeviceType = .mobile, + isVerified: Bool = false, + lastSeenIP: String?, + lastSeenTimestamp: TimeInterval = Date().timeIntervalSince1970) -> UserSessionInfo { + UserSessionInfo(sessionId: sessionId, + sessionName: sessionName, + deviceType: deviceType, + isVerified: isVerified, + lastSeenIP: lastSeenIP, + lastSeenTimestamp: lastSeenTimestamp) + + } + + private func sessionNameItem(sessionName: String) -> UserSessionDetailsSectionItemViewData { + UserSessionDetailsSectionItemViewData(title: VectorL10n.userSessionDetailsSessionName, + value: sessionName) + } + + private func sessionIdItem(sessionId: String) -> UserSessionDetailsSectionItemViewData { + UserSessionDetailsSectionItemViewData(title: VectorL10n.keyVerificationManuallyVerifyDeviceIdTitle, + value: sessionId) + } +} diff --git a/RiotTests/Modules/UserSessions/UserSession/UserSessionFlowCoordinatorTests.swift b/RiotTests/Modules/UserSessions/UserSession/UserSessionFlowCoordinatorTests.swift new file mode 100644 index 000000000..58de4db75 --- /dev/null +++ b/RiotTests/Modules/UserSessions/UserSession/UserSessionFlowCoordinatorTests.swift @@ -0,0 +1,77 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +@testable import Element + +class UserSessionFlowCoordinatorTests: XCTestCase { + + func test_start_shouldPushView() throws { + let userSessionInfo = UserSessionInfo(sessionId: "session", sessionName: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: Date().timeIntervalSince1970) + let navigationRouterSpy = NavigationRouterSpy() + let params = UserSessionFlowCoordinatorParameters(session: MXSession(), + navigationRouter: navigationRouterSpy, + userSessionInfo: userSessionInfo) + let sut = UserSessionFlowCoordinator(parameters: params) + sut.start() + XCTAssertNotNil(navigationRouterSpy.modulePushed) + } +} + +private class NavigationRouterSpy: NavigationRouterType { + + var modules: [Presentable] = [] + var modulePushed: Presentable? + + func present(_ module: Presentable, animated: Bool) { + } + + func dismissModule(animated: Bool, completion: (() -> Void)?) { + } + + func setRootModule(_ module: Presentable, hideNavigationBar: Bool, animated: Bool, popCompletion: (() -> Void)?) { + } + + func setModules(_ modules: [NavigationModule], hideNavigationBar: Bool, animated: Bool) { + } + + func popToRootModule(animated: Bool) { + } + + func popToModule(_ module: Presentable, animated: Bool) { + } + + func popModule(animated: Bool) { + } + + func push(_ module: Presentable, animated: Bool, popCompletion: (() -> Void)?) { + modulePushed = module + } + + func push(_ modules: [NavigationModule], animated: Bool) { + } + + func popAllModules(animated: Bool) { + } + + func contains(_ module: Presentable) -> Bool { + false + } + + func toPresentable() -> UIViewController { + UIViewController() + } +} From 6133814a10b4afe37b08efff871e89c69d8b8b13 Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Mon, 19 Sep 2022 15:35:58 +0200 Subject: [PATCH 024/104] New layout: Fixed low priority rooms titles obscured by bottom bar when side scrolling --- Riot/Modules/Home/AllChats/AllChatsViewController.swift | 8 ++++++++ changelog.d/6707.bugfix | 1 + 2 files changed, 9 insertions(+) create mode 100644 changelog.d/6707.bugfix diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index 7d33aa1fc..762175159 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -351,12 +351,20 @@ class AllChatsViewController: HomeViewController { } override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + guard scrollView == recentsTableView else { + return + } + initialScrollPosition = scrollPosition(of: scrollView) } override func scrollViewDidScroll(_ scrollView: UIScrollView) { super.scrollViewDidScroll(scrollView) + guard scrollView == recentsTableView else { + return + } + let scrollPosition = scrollPosition(of: scrollView) if !self.recentsTableView.isDragging && scrollPosition == 0 && self.navigationController?.isToolbarHidden == true { diff --git a/changelog.d/6707.bugfix b/changelog.d/6707.bugfix new file mode 100644 index 000000000..8bd333ed3 --- /dev/null +++ b/changelog.d/6707.bugfix @@ -0,0 +1 @@ +New layout: Fixed Low priority rooms titles obscured by bottom bar when side scrolling \ No newline at end of file From bc698c14b2029f840f794450393fe52241f07fe2 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Tue, 20 Sep 2022 09:43:14 +0300 Subject: [PATCH 025/104] UI tests --- .../Modules/Common/Mock/MockAppScreens.swift | 1 + .../UI/UserSessionDetailsViewUITests.swift | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/UI/UserSessionDetailsViewUITests.swift diff --git a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift index e52ac8e29..a87521164 100644 --- a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift +++ b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift @@ -20,6 +20,7 @@ import Foundation enum MockAppScreens { static let appScreens: [MockScreenState.Type] = [ MockUserSessionsOverviewScreenState.self, + MockUserSessionDetailsScreenState.self, MockLiveLocationLabPromotionScreenState.self, MockLiveLocationSharingViewerScreenState.self, MockAuthenticationLoginScreenState.self, diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/UI/UserSessionDetailsViewUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/UI/UserSessionDetailsViewUITests.swift new file mode 100644 index 000000000..4b59088e1 --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/UI/UserSessionDetailsViewUITests.swift @@ -0,0 +1,36 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +import RiotSwiftUI + +class UserSessionDetailsViewUITests: MockScreenTestCase { + + func test_longPressOnDetailsCell_CopyValueToClipboard() throws { + app.goToScreenWithIdentifier(MockUserSessionDetailsScreenState.allSections.title) + + UIPasteboard.general.string = "" + + let tables = app.tables + let sessionNameIosCell = tables.cells["Session name, iOS"] + sessionNameIosCell.press(forDuration: 0.5) + + app.buttons["Copy"].tap() + + let clipboard = try XCTUnwrap(UIPasteboard.general.string) + XCTAssertEqual(clipboard,"iOS") + } +} From 770657ee93e18936fed5a8a28cb2ed77009adab7 Mon Sep 17 00:00:00 2001 From: aringenbach Date: Tue, 20 Sep 2022 09:52:05 +0200 Subject: [PATCH 026/104] Fix replied event content parsing for nested replies --- .../Utils/EventFormatter/MXKEventFormatter.m | 12 +++++++++++- changelog.d/6729.bugfix | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 changelog.d/6729.bugfix diff --git a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m index fa6495fd5..63f86617a 100644 --- a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m +++ b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m @@ -1846,7 +1846,17 @@ static NSString *const kHTMLATagRegexPattern = @"( } else { - MXJSONModelSetString(repliedEventContent, repliedEvent.content[@"formatted_body"]); + MXReplyEventParser *parser = [[MXReplyEventParser alloc] init]; + MXReplyEventParts *parts = [parser parse:repliedEvent]; + MXJSONModelSetString(repliedEventContent, parts.formattedBodyParts.replyText) + if (!repliedEventContent) + { + MXJSONModelSetString(repliedEventContent, parts.bodyParts.replyText) + } + if (!repliedEventContent) + { + MXJSONModelSetString(repliedEventContent, repliedEvent.content[@"formatted_body"]); + } if (!repliedEventContent) { MXJSONModelSetString(repliedEventContent, repliedEvent.content[kMXMessageBodyKey]); diff --git a/changelog.d/6729.bugfix b/changelog.d/6729.bugfix new file mode 100644 index 000000000..214b20ab4 --- /dev/null +++ b/changelog.d/6729.bugfix @@ -0,0 +1 @@ +Fix replied event content parsing for nested replies From 264801b2e78bf863bd36b0eb38d89a92c77807d1 Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Tue, 20 Sep 2022 10:01:46 +0200 Subject: [PATCH 027/104] Fixed failed to join room (was not legal room) --- Riot/Modules/DeepLink/MXRoomAliasResolution+Deeplink.swift | 4 +--- .../DeepLink/MXRoomAliasResolutionDeeplinkTests.swift | 6 +++--- changelog.d/6653.bugfix | 1 + 3 files changed, 5 insertions(+), 6 deletions(-) create mode 100644 changelog.d/6653.bugfix diff --git a/Riot/Modules/DeepLink/MXRoomAliasResolution+Deeplink.swift b/Riot/Modules/DeepLink/MXRoomAliasResolution+Deeplink.swift index 234f07849..ddded6927 100644 --- a/Riot/Modules/DeepLink/MXRoomAliasResolution+Deeplink.swift +++ b/Riot/Modules/DeepLink/MXRoomAliasResolution+Deeplink.swift @@ -29,9 +29,7 @@ import MatrixSDK return nil } - return MXTools.encodeURIComponent( - fragment(for: roomId) - ) + return fragment(for: roomId) } private func fragment(for roomId: String) -> String { diff --git a/RiotTests/Modules/DeepLink/MXRoomAliasResolutionDeeplinkTests.swift b/RiotTests/Modules/DeepLink/MXRoomAliasResolutionDeeplinkTests.swift index 27116d070..03120ecbb 100644 --- a/RiotTests/Modules/DeepLink/MXRoomAliasResolutionDeeplinkTests.swift +++ b/RiotTests/Modules/DeepLink/MXRoomAliasResolutionDeeplinkTests.swift @@ -28,7 +28,7 @@ class MXRoomAliasResolutionDeeplinkTests: XCTestCase { let resolution = MXRoomAliasResolution() resolution.roomId = "!abc:matrix.org" - XCTAssertEqual(resolution.deeplinkFragment, "!abc%3Amatrix.org") + XCTAssertEqual(resolution.deeplinkFragment, "!abc:matrix.org") } func test_fragmentContainsSingleServer() { @@ -38,7 +38,7 @@ class MXRoomAliasResolutionDeeplinkTests: XCTestCase { "matrix.org" ] - XCTAssertEqual(resolution.deeplinkFragment, "xyz%3Aelement.io%3Fvia%3Dmatrix.org") + XCTAssertEqual(resolution.deeplinkFragment, "xyz:element.io?via=matrix.org") } func test_fragmentContainsMultipleSerivers() { @@ -51,6 +51,6 @@ class MXRoomAliasResolutionDeeplinkTests: XCTestCase { "matrix.org" ] - XCTAssertEqual(resolution.deeplinkFragment, "mno%3Aserver.com%3Fvia%3Dserver.com%26via%3Delement.io%26via%3Dwikipedia.org%26via%3Dmatrix.org") + XCTAssertEqual(resolution.deeplinkFragment, "mno:server.com?via=server.com&via=element.io&via=wikipedia.org&via=matrix.org") } } diff --git a/changelog.d/6653.bugfix b/changelog.d/6653.bugfix new file mode 100644 index 000000000..463ed14cd --- /dev/null +++ b/changelog.d/6653.bugfix @@ -0,0 +1 @@ +Fixed failed to join room (was not legal room) From cb1587af33c125ab1b563a0af971aeb54c22df60 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Tue, 20 Sep 2022 11:39:53 +0300 Subject: [PATCH 028/104] Moved, renamed unit tests --- .../Test/Unit}/UserSessionDetailsViewModelTests.swift | 6 +++--- .../Test/Unit}/UserSessionFlowCoordinatorTests.swift | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename {RiotTests/Modules/UserSessions/UserSession/DetailsView => RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/Unit}/UserSessionDetailsViewModelTests.swift (96%) rename {RiotTests/Modules/UserSessions/UserSession => RiotSwiftUI/Modules/UserSessions/UserSession/Test/Unit}/UserSessionFlowCoordinatorTests.swift (100%) diff --git a/RiotTests/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/Unit/UserSessionDetailsViewModelTests.swift similarity index 96% rename from RiotTests/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModelTests.swift rename to RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/Unit/UserSessionDetailsViewModelTests.swift index ca932d340..c0df66cf0 100644 --- a/RiotTests/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/Unit/UserSessionDetailsViewModelTests.swift @@ -20,7 +20,7 @@ import XCTest class UserSessionDetailsViewModelTests: XCTestCase { - func test_viewState_withSessionNameAndLastSeenIPNil_shouldReturnCorrectModel() { + func test_whenSessionNameAndLastSeenIPNil_viewStateCorrect() { let userSessionInfo = createUserSessionInfo(sessionId: "session", sessionName: nil, lastSeenIP: nil) @@ -38,7 +38,7 @@ class UserSessionDetailsViewModelTests: XCTestCase { XCTAssertEqual(sut.state, expectedModel) } - func test_viewState_withSessionNameNotNilLastSeenIPNil_shouldReturnCorrectModel() { + func test_whenSessionNameNotNilLastSeenIPNil_viewStateCorrect() { let userSessionInfo = createUserSessionInfo(sessionId: "session", sessionName: "session name", lastSeenIP: nil) @@ -58,7 +58,7 @@ class UserSessionDetailsViewModelTests: XCTestCase { XCTAssertEqual(sut.state, expectedModel) } - func test_viewState_withAllValues_shouldReturnCorrectModel() { + func test_whenUserSessionInfoContainsAllValues_viewStateCorrect() { let userSessionInfo = createUserSessionInfo(sessionId: "session", sessionName: "session name", lastSeenIP: "0.0.0.0") diff --git a/RiotTests/Modules/UserSessions/UserSession/UserSessionFlowCoordinatorTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/Test/Unit/UserSessionFlowCoordinatorTests.swift similarity index 100% rename from RiotTests/Modules/UserSessions/UserSession/UserSessionFlowCoordinatorTests.swift rename to RiotSwiftUI/Modules/UserSessions/UserSession/Test/Unit/UserSessionFlowCoordinatorTests.swift From 08f4a76ad91f401e7fc16a18021ba904860c1d4d Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Tue, 20 Sep 2022 11:40:32 +0300 Subject: [PATCH 029/104] Timeline composer layout loop fix (#6743) * Publish suitable view height for user suggestions * Update container view height * Add changelog * Fix user suggestions layout for pre iOS 16 --- Riot/Modules/Room/RoomViewController.h | 1 + Riot/Modules/Room/RoomViewController.m | 10 +++++ Riot/Modules/Room/RoomViewController.xib | 3 +- .../UserSuggestionCoordinator.swift | 41 ++++++++++++++++++- .../UserSuggestionCoordinatorBridge.swift | 5 +++ changelog.d/6734.bugfix | 1 + 6 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 changelog.d/6734.bugfix diff --git a/Riot/Modules/Room/RoomViewController.h b/Riot/Modules/Room/RoomViewController.h index 2b0ad02c5..a01f6c567 100644 --- a/Riot/Modules/Room/RoomViewController.h +++ b/Riot/Modules/Room/RoomViewController.h @@ -55,6 +55,7 @@ extern NSNotificationName const RoomGroupCallTileTappedNotification; // The preview header @property (weak, nonatomic, nullable) IBOutlet UIView *previewHeaderContainer; @property (weak, nonatomic, nullable) IBOutlet NSLayoutConstraint *previewHeaderContainerHeightConstraint; +@property (weak, nonatomic, nullable) IBOutlet NSLayoutConstraint *userSuggestionContainerHeightConstraint; // The jump to last unread banner @property (weak, nonatomic, nullable) IBOutlet UIView *jumpToLastUnreadBannerContainer; diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index cb0fb1a31..542007a41 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -7757,6 +7757,16 @@ static CGSize kThreadListBarButtonItemImageSize; [self mention:member]; } +- (void)userSuggestionCoordinatorBridge:(UserSuggestionCoordinatorBridge *)coordinator didUpdateViewHeight:(CGFloat)height +{ + if (self.userSuggestionContainerHeightConstraint.constant != height) + { + self.userSuggestionContainerHeightConstraint.constant = height; + + [self.view layoutIfNeeded]; + } +} + #pragma mark - ThreadsCoordinatorBridgePresenterDelegate - (void)threadsCoordinatorBridgePresenterDelegateDidComplete:(ThreadsCoordinatorBridgePresenter *)coordinatorBridgePresenter diff --git a/Riot/Modules/Room/RoomViewController.xib b/Riot/Modules/Room/RoomViewController.xib index 888aa3b75..073276604 100644 --- a/Riot/Modules/Room/RoomViewController.xib +++ b/Riot/Modules/Room/RoomViewController.xib @@ -32,6 +32,7 @@ + @@ -171,7 +172,7 @@ - + diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift index 4d10fce83..fd7bd57b3 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift @@ -17,9 +17,11 @@ import Foundation import UIKit import SwiftUI +import Combine protocol UserSuggestionCoordinatorDelegate: AnyObject { func userSuggestionCoordinator(_ coordinator: UserSuggestionCoordinator, didRequestMentionForMember member: MXRoomMember, textTrigger: String?) + func userSuggestionCoordinator(_ coordinator: UserSuggestionCoordinator, didUpdateViewHeight height: CGFloat) } struct UserSuggestionCoordinatorParameters { @@ -35,10 +37,12 @@ final class UserSuggestionCoordinator: Coordinator, Presentable { private let parameters: UserSuggestionCoordinatorParameters - private var userSuggestionHostingController: UIViewController + private var userSuggestionHostingController: UIHostingController private var userSuggestionService: UserSuggestionServiceProtocol private var userSuggestionViewModel: UserSuggestionViewModelProtocol private var roomMemberProvider: UserSuggestionCoordinatorRoomMemberProvider + + private var cancellables = Set() // MARK: Public @@ -77,12 +81,18 @@ final class UserSuggestionCoordinator: Coordinator, Presentable { self.delegate?.userSuggestionCoordinator(self, didRequestMentionForMember: member, textTrigger: self.userSuggestionService.currentTextTrigger) } } + + userSuggestionService.items.sink { [weak self] _ in + guard let self = self else { return } + self.delegate?.userSuggestionCoordinator(self, + didUpdateViewHeight: self.calculateViewHeight()) + }.store(in: &cancellables) } func processTextMessage(_ textMessage: String) { userSuggestionService.processTextMessage(textMessage) } - + // MARK: - Public func start() { @@ -91,6 +101,33 @@ final class UserSuggestionCoordinator: Coordinator, Presentable { func toPresentable() -> UIViewController { return self.userSuggestionHostingController } + + // MARK: - Private + + private func calculateViewHeight() -> CGFloat { + let viewModel = UserSuggestionViewModel(userSuggestionService: userSuggestionService) + let view = UserSuggestionList(viewModel: viewModel.context) + .addDependency(AvatarService.instantiate(mediaManager: parameters.mediaManager)) + + let controller = VectorHostingController(rootView: view) + guard let view = controller.view else { + return 0 + } + view.isHidden = true + + toPresentable().view.addSubview(view) + controller.didMove(toParent: toPresentable()) + + view.setNeedsLayout() + view.layoutIfNeeded() + + let result = view.intrinsicContentSize.height + + controller.didMove(toParent: nil) + view.removeFromSuperview() + + return result + } } private class UserSuggestionCoordinatorRoomMemberProvider: RoomMembersProviderProtocol { diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinatorBridge.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinatorBridge.swift index 1fd88e25b..44ddaee9f 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinatorBridge.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinatorBridge.swift @@ -19,6 +19,7 @@ import Foundation @objc protocol UserSuggestionCoordinatorBridgeDelegate: AnyObject { func userSuggestionCoordinatorBridge(_ coordinator: UserSuggestionCoordinatorBridge, didRequestMentionForMember member: MXRoomMember, textTrigger: String?) + func userSuggestionCoordinatorBridge(_ coordinator: UserSuggestionCoordinatorBridge, didUpdateViewHeight height: CGFloat) } @objcMembers @@ -54,4 +55,8 @@ extension UserSuggestionCoordinatorBridge: UserSuggestionCoordinatorDelegate { func userSuggestionCoordinator(_ coordinator: UserSuggestionCoordinator, didRequestMentionForMember member: MXRoomMember, textTrigger: String?) { delegate?.userSuggestionCoordinatorBridge(self, didRequestMentionForMember: member, textTrigger: textTrigger) } + + func userSuggestionCoordinator(_ coordinator: UserSuggestionCoordinator, didUpdateViewHeight height: CGFloat) { + delegate?.userSuggestionCoordinatorBridge(self, didUpdateViewHeight: height) + } } diff --git a/changelog.d/6734.bugfix b/changelog.d/6734.bugfix new file mode 100644 index 000000000..feec00f69 --- /dev/null +++ b/changelog.d/6734.bugfix @@ -0,0 +1 @@ +Room: Fix a composer crash after long unsent messages. From a4b41e3b5e5e8029c5adc72d6b768ab4e363e870 Mon Sep 17 00:00:00 2001 From: gulekismail Date: Tue, 20 Sep 2022 13:46:45 +0300 Subject: [PATCH 030/104] Prepare for new sprint --- Config/AppVersion.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index 160b9dc81..7e347c68e 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.9.6 -CURRENT_PROJECT_VERSION = 1.9.6 +MARKETING_VERSION = 1.9.7 +CURRENT_PROJECT_VERSION = 1.9.7 From 8a564c44441d90d8dc9db54a4c45ed0dc284af11 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Tue, 20 Sep 2022 14:10:51 +0300 Subject: [PATCH 031/104] Prepare for new sprint --- changelog.d/6584.bugfix | 1 - changelog.d/6653.bugfix | 1 - changelog.d/6654.bugfix | 1 - changelog.d/6705.bugfix | 1 - changelog.d/6707.bugfix | 1 - changelog.d/6708.bugfix | 1 - changelog.d/6709.bugfix | 1 - changelog.d/6712.bugfix | 1 - changelog.d/6722.bugfix | 1 - changelog.d/6729.bugfix | 1 - changelog.d/6734.bugfix | 1 - changelog.d/6739.bugfix | 1 - changelog.d/pr-6726.change | 1 - 13 files changed, 13 deletions(-) delete mode 100644 changelog.d/6584.bugfix delete mode 100644 changelog.d/6653.bugfix delete mode 100644 changelog.d/6654.bugfix delete mode 100644 changelog.d/6705.bugfix delete mode 100644 changelog.d/6707.bugfix delete mode 100644 changelog.d/6708.bugfix delete mode 100644 changelog.d/6709.bugfix delete mode 100644 changelog.d/6712.bugfix delete mode 100644 changelog.d/6722.bugfix delete mode 100644 changelog.d/6729.bugfix delete mode 100644 changelog.d/6734.bugfix delete mode 100644 changelog.d/6739.bugfix delete mode 100644 changelog.d/pr-6726.change diff --git a/changelog.d/6584.bugfix b/changelog.d/6584.bugfix deleted file mode 100644 index 7fefaaff1..000000000 --- a/changelog.d/6584.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix crash when some opus audio files are added to a room. diff --git a/changelog.d/6653.bugfix b/changelog.d/6653.bugfix deleted file mode 100644 index 463ed14cd..000000000 --- a/changelog.d/6653.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixed failed to join room (was not legal room) diff --git a/changelog.d/6654.bugfix b/changelog.d/6654.bugfix deleted file mode 100644 index 1b342f283..000000000 --- a/changelog.d/6654.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix crash presenting Sign Out or Invite to Element menu items on iPad. diff --git a/changelog.d/6705.bugfix b/changelog.d/6705.bugfix deleted file mode 100644 index 9fd360053..000000000 --- a/changelog.d/6705.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix crash on logout when syncing is currently in progress diff --git a/changelog.d/6707.bugfix b/changelog.d/6707.bugfix deleted file mode 100644 index 8bd333ed3..000000000 --- a/changelog.d/6707.bugfix +++ /dev/null @@ -1 +0,0 @@ -New layout: Fixed Low priority rooms titles obscured by bottom bar when side scrolling \ No newline at end of file diff --git a/changelog.d/6708.bugfix b/changelog.d/6708.bugfix deleted file mode 100644 index 10961f90f..000000000 --- a/changelog.d/6708.bugfix +++ /dev/null @@ -1 +0,0 @@ -Message Composer: Stop the keyboard jumping after sending a message on certain devices. diff --git a/changelog.d/6709.bugfix b/changelog.d/6709.bugfix deleted file mode 100644 index 3d6e2d0e6..000000000 --- a/changelog.d/6709.bugfix +++ /dev/null @@ -1 +0,0 @@ -New App Layout: Make sure the green loading spinner is dismissed after clearing the cache. diff --git a/changelog.d/6712.bugfix b/changelog.d/6712.bugfix deleted file mode 100644 index c1276e052..000000000 --- a/changelog.d/6712.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix a crash when previewing a room. \ No newline at end of file diff --git a/changelog.d/6722.bugfix b/changelog.d/6722.bugfix deleted file mode 100644 index 9a7b6dd8f..000000000 --- a/changelog.d/6722.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix login crash on Xcode 14 builds diff --git a/changelog.d/6729.bugfix b/changelog.d/6729.bugfix deleted file mode 100644 index 214b20ab4..000000000 --- a/changelog.d/6729.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix replied event content parsing for nested replies diff --git a/changelog.d/6734.bugfix b/changelog.d/6734.bugfix deleted file mode 100644 index feec00f69..000000000 --- a/changelog.d/6734.bugfix +++ /dev/null @@ -1 +0,0 @@ -Room: Fix a composer crash after long unsent messages. diff --git a/changelog.d/6739.bugfix b/changelog.d/6739.bugfix deleted file mode 100644 index 0abdd05b5..000000000 --- a/changelog.d/6739.bugfix +++ /dev/null @@ -1 +0,0 @@ -New App Layout: fixed rooms list doesn't update after log out and log into another account diff --git a/changelog.d/pr-6726.change b/changelog.d/pr-6726.change deleted file mode 100644 index 5c1dfa81d..000000000 --- a/changelog.d/pr-6726.change +++ /dev/null @@ -1 +0,0 @@ -Sentry: Disable unnecessary network breadcrumbs From a0ef2ea414f6dc8a6cf0b909b4201d5a1687f6f8 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Tue, 20 Sep 2022 15:53:28 +0300 Subject: [PATCH 032/104] Changelog --- changelog.d/6693.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6693.wip diff --git a/changelog.d/6693.wip b/changelog.d/6693.wip new file mode 100644 index 000000000..a50a16691 --- /dev/null +++ b/changelog.d/6693.wip @@ -0,0 +1 @@ +Device manager: User session details screen. From 98363c1af16639c45a2a14215e32a75a81b32de5 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Tue, 20 Sep 2022 16:00:35 +0300 Subject: [PATCH 033/104] Cleanup --- .../MockUserSessionDetailsScreenState.swift | 14 ++++++++++++-- .../Test/UI/UserSessionDetailsViewUITests.swift | 2 +- .../Unit/UserSessionDetailsViewModelTests.swift | 1 - .../DetailsView/UserSessionDetailsViewModel.swift | 6 +----- .../Unit/UserSessionFlowCoordinatorTests.swift | 7 ++++++- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/MockUserSessionDetailsScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/MockUserSessionDetailsScreenState.swift index d055cb60d..9424264eb 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/MockUserSessionDetailsScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/MockUserSessionDetailsScreenState.swift @@ -41,9 +41,19 @@ enum MockUserSessionDetailsScreenState: MockScreenState, CaseIterable { let currentSessionInfo: UserSessionInfo switch self { case .allSections: - currentSessionInfo = UserSessionInfo(sessionId: "session", sessionName: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + currentSessionInfo = UserSessionInfo(sessionId: "session", + sessionName: "iOS", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100) case .sessionSectionOnly: - currentSessionInfo = UserSessionInfo(sessionId: "session", sessionName: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: nil, lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + currentSessionInfo = UserSessionInfo(sessionId: "session", + sessionName: "iOS", + deviceType: .mobile, + isVerified: false, + lastSeenIP: nil, + lastSeenTimestamp: Date().timeIntervalSince1970 - 100) } let viewModel = UserSessionDetailsViewModel(userSessionInfo: currentSessionInfo) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/UI/UserSessionDetailsViewUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/UI/UserSessionDetailsViewUITests.swift index 4b59088e1..11c979943 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/UI/UserSessionDetailsViewUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/UI/UserSessionDetailsViewUITests.swift @@ -19,7 +19,7 @@ import RiotSwiftUI class UserSessionDetailsViewUITests: MockScreenTestCase { - func test_longPressOnDetailsCell_CopyValueToClipboard() throws { + func test_longPressDetailsCell_CopiesValueToClipboard() throws { app.goToScreenWithIdentifier(MockUserSessionDetailsScreenState.allSections.title) UIPasteboard.general.string = "" diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/Unit/UserSessionDetailsViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/Unit/UserSessionDetailsViewModelTests.swift index c0df66cf0..dfcd6d4de 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/Unit/UserSessionDetailsViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/Unit/UserSessionDetailsViewModelTests.swift @@ -19,7 +19,6 @@ import XCTest class UserSessionDetailsViewModelTests: XCTestCase { - func test_whenSessionNameAndLastSeenIPNil_viewStateCorrect() { let userSessionInfo = createUserSessionInfo(sessionId: "session", sessionName: nil, diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift index b8481ce3e..f316f0e84 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift @@ -60,11 +60,7 @@ class UserSessionDetailsViewModel: UserSessionDetailsViewModelType { } // MARK: - Public - - override func process(viewAction: UserSessionDetailsViewAction) { - - } - + // MARK: - Private } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/Test/Unit/UserSessionFlowCoordinatorTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/Test/Unit/UserSessionFlowCoordinatorTests.swift index 58de4db75..c0a85a6dc 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/Test/Unit/UserSessionFlowCoordinatorTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/Test/Unit/UserSessionFlowCoordinatorTests.swift @@ -20,7 +20,12 @@ import XCTest class UserSessionFlowCoordinatorTests: XCTestCase { func test_start_shouldPushView() throws { - let userSessionInfo = UserSessionInfo(sessionId: "session", sessionName: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: Date().timeIntervalSince1970) + let userSessionInfo = UserSessionInfo(sessionId: "session", + sessionName: "iOS", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: Date().timeIntervalSince1970) let navigationRouterSpy = NavigationRouterSpy() let params = UserSessionFlowCoordinatorParameters(session: MXSession(), navigationRouter: navigationRouterSpy, From a6dc967a23a590b443d7a2bf6b96750df0da722e Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Tue, 20 Sep 2022 16:10:57 +0300 Subject: [PATCH 034/104] Refactored UserSessionDetailsViewModel --- .../UserSessionDetailsViewModel.swift | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift index f316f0e84..f99af5445 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift @@ -27,40 +27,58 @@ class UserSessionDetailsViewModel: UserSessionDetailsViewModelType { // MARK: Private // MARK: Public - + // MARK: - Setup init(userSessionInfo: UserSessionInfo) { + super.init(initialViewState: UserSessionDetailsViewState(sections: [])) + updateViewState(userSessionInfo: userSessionInfo) + } + + // MARK: - Public + + // MARK: - Private + + private func updateViewState(userSessionInfo: UserSessionInfo) { var sections = [UserSessionDetailsSectionViewData]() + sections.append(sessionSection(userSessionInfo: userSessionInfo)) + + if let deviceSection = deviceSection(userSessionInfo: userSessionInfo) { + sections.append(deviceSection) + } + + state = UserSessionDetailsViewState(sections: sections) + } + + private func sessionSection(userSessionInfo: UserSessionInfo) -> UserSessionDetailsSectionViewData { var sessionItems = [UserSessionDetailsSectionItemViewData]() + if let sessionName = userSessionInfo.sessionName { sessionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.userSessionDetailsSessionName, value: sessionName)) } + sessionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.keyVerificationManuallyVerifyDeviceIdTitle, value: userSessionInfo.sessionId)) - sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader, - footer: VectorL10n.userSessionDetailsSessionSectionFooter, - items: sessionItems)) + return UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader, + footer: VectorL10n.userSessionDetailsSessionSectionFooter, + items: sessionItems) + } + + private func deviceSection(userSessionInfo: UserSessionInfo) -> UserSessionDetailsSectionViewData? { var deviceSectionItems = [UserSessionDetailsSectionItemViewData]() if let lastSeenIP = userSessionInfo.lastSeenIP { deviceSectionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.userSessionDetailsDeviceIpAddress, value: lastSeenIP)) } if deviceSectionItems.count > 0 { - sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsDeviceSectionHeader, - footer: nil, - items: deviceSectionItems)) + return UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsDeviceSectionHeader, + footer: nil, + items: deviceSectionItems) } - - let initialViewState = UserSessionDetailsViewState(sections: sections) - super.init(initialViewState: initialViewState) + return nil } - - // MARK: - Public - - // MARK: - Private } From 51769c98846de4691bb5cb7d28278bac7d9cf892 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Tue, 20 Sep 2022 21:19:28 +0200 Subject: [PATCH 035/104] Fix crash when scrolling chat list Fixes: #6749 --- Riot/Modules/Home/AllChats/AllChatsViewController.swift | 2 +- changelog.d/6749.bugfix | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/6749.bugfix diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index d1feec95a..291ea8322 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -352,7 +352,7 @@ class AllChatsViewController: HomeViewController { private var initialScrollPosition: Double = 0 private func scrollPosition(of scrollView: UIScrollView) -> Double { - return scrollView.contentOffset.y + scrollView.adjustedContentInset.top + scrollView.adjustedContentInset.bottom + return scrollView.contentOffset.y + scrollView.adjustedContentInset.top } override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { diff --git a/changelog.d/6749.bugfix b/changelog.d/6749.bugfix new file mode 100644 index 000000000..e84ee6f0c --- /dev/null +++ b/changelog.d/6749.bugfix @@ -0,0 +1 @@ +Fix crash when scrolling chat list From 8b4855206fa26869964815a36ba967edb5c56a77 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Thu, 22 Sep 2022 11:52:42 +0300 Subject: [PATCH 036/104] Code review fixes --- .../UserSessionFlowCoordinatorTests.swift | 82 ------------------- .../UserSessionFlowCoordinator.swift | 78 ------------------ .../UserSessionDetailsCoordinator.swift | 71 ++++++++++++++++ .../MockUserSessionDetailsScreenState.swift | 6 +- .../Test/UI/UserSessionDetailsUITests.swift} | 6 +- .../UserSessionDetailsViewModelTests.swift | 5 +- .../UserSessionDetailsModels.swift | 8 +- .../UserSessionDetailsViewModel.swift | 9 +- .../UserSessionDetailsViewModelProtocol.swift | 23 ++++++ .../View/UserSessionDetails.swift} | 8 +- .../View/UserSessionDetailsItem.swift} | 20 ++--- .../UserSessionsFlowCoordinator.swift | 61 +++++++++----- .../UserSessionsOverviewCoordinator.swift | 14 +--- .../UserSessionsOverviewModels.swift | 5 +- 14 files changed, 178 insertions(+), 218 deletions(-) delete mode 100644 RiotSwiftUI/Modules/UserSessions/UserSession/Test/Unit/UserSessionFlowCoordinatorTests.swift delete mode 100644 RiotSwiftUI/Modules/UserSessions/UserSession/UserSessionFlowCoordinator.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift rename RiotSwiftUI/Modules/UserSessions/{UserSession/DetailsView => UserSessionDetails}/MockUserSessionDetailsScreenState.swift (94%) rename RiotSwiftUI/Modules/UserSessions/{UserSession/DetailsView/Test/UI/UserSessionDetailsViewUITests.swift => UserSessionDetails/Test/UI/UserSessionDetailsUITests.swift} (92%) rename RiotSwiftUI/Modules/UserSessions/{UserSession/DetailsView => UserSessionDetails}/Test/Unit/UserSessionDetailsViewModelTests.swift (98%) rename RiotSwiftUI/Modules/UserSessions/{UserSession/DetailsView => UserSessionDetails}/UserSessionDetailsModels.swift (94%) rename RiotSwiftUI/Modules/UserSessions/{UserSession/DetailsView => UserSessionDetails}/UserSessionDetailsViewModel.swift (95%) create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModelProtocol.swift rename RiotSwiftUI/Modules/UserSessions/{UserSession/DetailsView/UserSessionDetailsView.swift => UserSessionDetails/View/UserSessionDetails.swift} (92%) rename RiotSwiftUI/Modules/UserSessions/{UserSession/DetailsView/UserSessionDetailsItemView.swift => UserSessionDetails/View/UserSessionDetailsItem.swift} (75%) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/Test/Unit/UserSessionFlowCoordinatorTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/Test/Unit/UserSessionFlowCoordinatorTests.swift deleted file mode 100644 index c0a85a6dc..000000000 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/Test/Unit/UserSessionFlowCoordinatorTests.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// Copyright 2022 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import XCTest -@testable import Element - -class UserSessionFlowCoordinatorTests: XCTestCase { - - func test_start_shouldPushView() throws { - let userSessionInfo = UserSessionInfo(sessionId: "session", - sessionName: "iOS", - deviceType: .mobile, - isVerified: false, - lastSeenIP: "10.0.0.10", - lastSeenTimestamp: Date().timeIntervalSince1970) - let navigationRouterSpy = NavigationRouterSpy() - let params = UserSessionFlowCoordinatorParameters(session: MXSession(), - navigationRouter: navigationRouterSpy, - userSessionInfo: userSessionInfo) - let sut = UserSessionFlowCoordinator(parameters: params) - sut.start() - XCTAssertNotNil(navigationRouterSpy.modulePushed) - } -} - -private class NavigationRouterSpy: NavigationRouterType { - - var modules: [Presentable] = [] - var modulePushed: Presentable? - - func present(_ module: Presentable, animated: Bool) { - } - - func dismissModule(animated: Bool, completion: (() -> Void)?) { - } - - func setRootModule(_ module: Presentable, hideNavigationBar: Bool, animated: Bool, popCompletion: (() -> Void)?) { - } - - func setModules(_ modules: [NavigationModule], hideNavigationBar: Bool, animated: Bool) { - } - - func popToRootModule(animated: Bool) { - } - - func popToModule(_ module: Presentable, animated: Bool) { - } - - func popModule(animated: Bool) { - } - - func push(_ module: Presentable, animated: Bool, popCompletion: (() -> Void)?) { - modulePushed = module - } - - func push(_ modules: [NavigationModule], animated: Bool) { - } - - func popAllModules(animated: Bool) { - } - - func contains(_ module: Presentable) -> Bool { - false - } - - func toPresentable() -> UIViewController { - UIViewController() - } -} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/UserSessionFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSession/UserSessionFlowCoordinator.swift deleted file mode 100644 index c5dd499dc..000000000 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/UserSessionFlowCoordinator.swift +++ /dev/null @@ -1,78 +0,0 @@ -// File created from FlowTemplate -// $ createRootCoordinator.sh Folder UserSessionFlow UserSessionOverview -/* - Copyright 2021 New Vector Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import CommonKit -import UIKit - -struct UserSessionFlowCoordinatorParameters { - let session: MXSession - let navigationRouter: NavigationRouterType - let userSessionInfo: UserSessionInfo -} - -final class UserSessionFlowCoordinator: Coordinator, Presentable { - - // MARK: - Properties - - // MARK: Private - - private let parameters: UserSessionFlowCoordinatorParameters - private let userSessionDetailsViewHostingController: UIViewController - private var userSessionDetailsViewModel: UserSessionDetailsViewModel - - private var navigationRouter: NavigationRouterType { - return self.parameters.navigationRouter - } - - // MARK: Public - - // Must be used only internally - var childCoordinators: [Coordinator] = [] - - // MARK: - Setup - - init(parameters: UserSessionFlowCoordinatorParameters) { - self.parameters = parameters - - let viewModel = UserSessionDetailsViewModel(userSessionInfo: parameters.userSessionInfo) - let view = UserSessionDetailsView(viewModel: viewModel.context) - userSessionDetailsViewModel = viewModel - userSessionDetailsViewHostingController = VectorHostingController(rootView: view) - } - - // MARK: - Public - - func start() { - // TODO: change to showUserSessionOverview() - showUserSessionDetails() - } - - private func showUserSessionOverview() { - // TODO: PSG-690 - } - - private func showUserSessionDetails() { - self.navigationRouter.push(self.userSessionDetailsViewHostingController, animated: true, popCompletion: nil) - } - - func toPresentable() -> UIViewController { - return self.navigationRouter.toPresentable() - } - - // MARK: - Private -} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift new file mode 100644 index 000000000..378a53c6c --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift @@ -0,0 +1,71 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI +import CommonKit + +struct UserSessionDetailsCoordinatorParameters { + let session: MXSession + let userSessionInfo: UserSessionInfo +} + +final class UserSessionDetailsCoordinator: Coordinator, Presentable { + + // MARK: - Properties + + // MARK: Private + + private let parameters: UserSessionDetailsCoordinatorParameters + private let userSessionDetailsHostingController: UIViewController + private var userSessionDetailsViewModel: UserSessionDetailsViewModelProtocol + + private var indicatorPresenter: UserIndicatorTypePresenterProtocol + private var loadingIndicator: UserIndicator? + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + var completion: ((UserSessionDetailsViewModelResult) -> Void)? + + // MARK: - Setup + + init(parameters: UserSessionDetailsCoordinatorParameters) { + self.parameters = parameters + + let viewModel = UserSessionDetailsViewModel(userSessionInfo: parameters.userSessionInfo) + let view = UserSessionDetails(viewModel: viewModel.context) + userSessionDetailsViewModel = viewModel + userSessionDetailsHostingController = VectorHostingController(rootView: view) + + indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: userSessionDetailsHostingController) + } + + // MARK: - Public + + func start() { + MXLog.debug("[UserSessionDetailsCoordinator] did start.") + userSessionDetailsViewModel.completion = { [weak self] result in + guard let self = self else { return } + MXLog.debug("[UserSessionDetailsCoordinator] UserSessionDetailsViewModel did complete with result: \(result).") + self.completion?(result) + } + } + + func toPresentable() -> UIViewController { + return self.userSessionDetailsHostingController + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/MockUserSessionDetailsScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift similarity index 94% rename from RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/MockUserSessionDetailsScreenState.swift rename to RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift index 9424264eb..317b2d015 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/MockUserSessionDetailsScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift @@ -1,5 +1,5 @@ // -// Copyright 2022 New Vector Ltd +// Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ enum MockUserSessionDetailsScreenState: MockScreenState, CaseIterable { /// The associated screen var screenType: Any.Type { - UserSessionDetailsView.self + UserSessionDetails.self } /// A list of screen state definitions @@ -61,7 +61,7 @@ enum MockUserSessionDetailsScreenState: MockScreenState, CaseIterable { return ( [currentSessionInfo], - AnyView(UserSessionDetailsView(viewModel: viewModel.context)) + AnyView(UserSessionDetails(viewModel: viewModel.context)) ) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/UI/UserSessionDetailsViewUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/UI/UserSessionDetailsUITests.swift similarity index 92% rename from RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/UI/UserSessionDetailsViewUITests.swift rename to RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/UI/UserSessionDetailsUITests.swift index 11c979943..60c5308f4 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/UI/UserSessionDetailsViewUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/UI/UserSessionDetailsUITests.swift @@ -1,5 +1,5 @@ -// -// Copyright 2022 New Vector Ltd +// +// Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ import XCTest import RiotSwiftUI -class UserSessionDetailsViewUITests: MockScreenTestCase { +class UserSessionDetailsUITests: MockScreenTestCase { func test_longPressDetailsCell_CopiesValueToClipboard() throws { app.goToScreenWithIdentifier(MockUserSessionDetailsScreenState.allSections.title) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/Unit/UserSessionDetailsViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift similarity index 98% rename from RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/Unit/UserSessionDetailsViewModelTests.swift rename to RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift index dfcd6d4de..c5aa629b5 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/Test/Unit/UserSessionDetailsViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift @@ -1,5 +1,5 @@ // -// Copyright 2022 New Vector Ltd +// Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,7 +15,8 @@ // import XCTest -@testable import Element + +@testable import RiotSwiftUI class UserSessionDetailsViewModelTests: XCTestCase { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsModels.swift similarity index 94% rename from RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsModels.swift rename to RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsModels.swift index c87f7c17f..38d4cf31a 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsModels.swift @@ -1,5 +1,5 @@ // -// Copyright 2022 New Vector Ltd +// Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,9 +16,15 @@ import Foundation +// MARK: - Coordinator + +// MARK: View model + enum UserSessionDetailsViewModelResult { } +// MARK: View + enum UserSessionDetailsViewAction { } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift similarity index 95% rename from RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift rename to RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift index f99af5445..c4b04bfec 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift @@ -1,5 +1,5 @@ -// -// Copyright 2022 New Vector Ltd +// +// Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ typealias UserSessionDetailsViewModelType = StateStoreViewModel -class UserSessionDetailsViewModel: UserSessionDetailsViewModelType { +class UserSessionDetailsViewModel: UserSessionDetailsViewModelType, UserSessionDetailsViewModelProtocol { // MARK: - Properties @@ -28,6 +28,8 @@ class UserSessionDetailsViewModel: UserSessionDetailsViewModelType { // MARK: Public + var completion: ((UserSessionDetailsViewModelResult) -> Void)? + // MARK: - Setup init(userSessionInfo: UserSessionInfo) { @@ -81,4 +83,3 @@ class UserSessionDetailsViewModel: UserSessionDetailsViewModelType { return nil } } - diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModelProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModelProtocol.swift new file mode 100644 index 000000000..8222cc770 --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModelProtocol.swift @@ -0,0 +1,23 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +protocol UserSessionDetailsViewModelProtocol { + + var completion: ((UserSessionDetailsViewModelResult) -> Void)? { get set } + var context: UserSessionDetailsViewModelType.Context { get } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsView.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetails.swift similarity index 92% rename from RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsView.swift rename to RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetails.swift index 1e851f377..3be803271 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsView.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetails.swift @@ -1,5 +1,5 @@ -// -// Copyright 2022 New Vector Ltd +// +// Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ import SwiftUI -struct UserSessionDetailsView: View { +struct UserSessionDetails: View { // MARK: - Properties @@ -38,7 +38,7 @@ struct UserSessionDetailsView: View { ForEach(viewModel.viewState.sections) { section in SwiftUI.Section { ForEach(section.items) { item in - UserSessionDetailsItemView(viewData: item, horizontalPadding: LayoutConstants.listItemHorizontalPadding) + UserSessionDetailsItem(viewData: item, horizontalPadding: LayoutConstants.listItemHorizontalPadding) .listRowInsets(EdgeInsets()) } } header: { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsItemView.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetailsItem.swift similarity index 75% rename from RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsItemView.swift rename to RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetailsItem.swift index ca9d4b9e0..870ac606a 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSession/DetailsView/UserSessionDetailsItemView.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetailsItem.swift @@ -16,7 +16,7 @@ import SwiftUI -struct UserSessionDetailsItemView: View { +struct UserSessionDetailsItem: View { // MARK: - Properties @@ -60,26 +60,26 @@ struct UserSessionDetailsItemView: View { // MARK: - Previews -struct UserSessionDetailsItemView_Previews: PreviewProvider { +struct UserSessionDetailsItem_Previews: PreviewProvider { static var previews: some View { Group { List { - UserSessionDetailsItemView(viewData: UserSessionDetailsSectionItemViewData(title: "Session name", - value: "Element Web: Firefox on macOS")) + UserSessionDetailsItem(viewData: UserSessionDetailsSectionItemViewData(title: "Session name", + value: "Element Web: Firefox on macOS")) .listRowInsets(EdgeInsets()) - UserSessionDetailsItemView(viewData: UserSessionDetailsSectionItemViewData(title: "Session ID", - value: "76c95352559d-react-7c57680b93db-js-b64dbdce74b0")) + UserSessionDetailsItem(viewData: UserSessionDetailsSectionItemViewData(title: "Session ID", + value: "76c95352559d-react-7c57680b93db-js-b64dbdce74b0")) .listRowInsets(EdgeInsets()) } .preferredColorScheme(.light) .listStyle(.grouped) List { - UserSessionDetailsItemView(viewData: UserSessionDetailsSectionItemViewData(title: "Session name", - value: "Element Web: Firefox on macOS")) + UserSessionDetailsItem(viewData: UserSessionDetailsSectionItemViewData(title: "Session name", + value: "Element Web: Firefox on macOS")) .listRowInsets(EdgeInsets()) - UserSessionDetailsItemView(viewData: UserSessionDetailsSectionItemViewData(title: "Session ID", - value: "76c95352559d-react-7c57680b93db-js-b64dbdce74b0")) + UserSessionDetailsItem(viewData: UserSessionDetailsSectionItemViewData(title: "Session ID", + value: "76c95352559d-react-7c57680b93db-js-b64dbdce74b0")) .listRowInsets(EdgeInsets()) } .preferredColorScheme(.dark) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift index 7347294ee..b4835c8e2 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift @@ -31,7 +31,7 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { private let navigationRouter: NavigationRouterType // MARK: Public - + // Must be used only internally var childCoordinators: [Coordinator] = [] var completion: (() -> Void)? @@ -44,20 +44,54 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { self.navigationRouter = parameters.router ?? NavigationRouter(navigationController: RiotNavigationController()) } + // MARK: - Private + + private func pushScreen(with coordinator: Coordinator & Presentable) { + add(childCoordinator: coordinator) + + self.navigationRouter.push(coordinator, animated: true, popCompletion: { [weak self] in + self?.remove(childCoordinator: coordinator) + }) + + coordinator.start() + } + + private func createUserSessionsOverviewCoordinator() -> UserSessionsOverviewCoordinator { + let parameters = UserSessionsOverviewCoordinatorParameters(session: self.parameters.session) + + let coordinator = UserSessionsOverviewCoordinator(parameters: parameters) + coordinator.completion = { [weak self] result in + guard let self = self else { return } + switch result { + case let .openSessionDetails(session: session): + self.openSessionDetails(session: session) + } + } + return coordinator + } + + private func openSessionDetails(session: UserSessionInfo) { + let coordinator = createUserSessionDetailsCoordinator(session: session) + pushScreen(with: coordinator) + } + + private func createUserSessionDetailsCoordinator(session: UserSessionInfo) -> UserSessionDetailsCoordinator { + let parameters = UserSessionDetailsCoordinatorParameters( + session: parameters.session, + userSessionInfo: session) + return UserSessionDetailsCoordinator(parameters: parameters) + } + // MARK: - Public func start() { MXLog.debug("[UserSessionsFlowCoordinator] did start.") - let rootCoordinatorParameters = UserSessionsOverviewCoordinatorParameters(session: self.parameters.session) - - let rootCoordinator = UserSessionsOverviewCoordinator(parameters: rootCoordinatorParameters) - rootCoordinator.delegate = self - + let rootCoordinator = createUserSessionsOverviewCoordinator() rootCoordinator.start() - + self.add(childCoordinator: rootCoordinator) - + if self.navigationRouter.modules.isEmpty == false { self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in self?.remove(childCoordinator: rootCoordinator) @@ -75,14 +109,3 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { return self.navigationRouter.toPresentable() } } - -extension UserSessionsFlowCoordinator: UserSessionsOverviewCoordinatorDelegate { - func showUserSessionOverview(session: UserSessionInfo) { - let parameters = UserSessionFlowCoordinatorParameters(session: parameters.session, - navigationRouter: navigationRouter, - userSessionInfo: session) - let coordinator = UserSessionFlowCoordinator(parameters: parameters) - coordinator.start() - self.add(childCoordinator: coordinator) - } -} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift index e36a6e240..f8726ba5c 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift @@ -21,10 +21,6 @@ struct UserSessionsOverviewCoordinatorParameters { let session: MXSession } -protocol UserSessionsOverviewCoordinatorDelegate: AnyObject { - func showUserSessionOverview(session: UserSessionInfo) -} - final class UserSessionsOverviewCoordinator: Coordinator, Presentable { // MARK: - Properties @@ -43,10 +39,8 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { // Must be used only internally var childCoordinators: [Coordinator] = [] - var completion: (() -> Void)? - - weak var delegate: UserSessionsOverviewCoordinatorDelegate? - + var completion: ((UserSessionsOverviewCoordinatorResult) -> Void)? + // MARK: - Setup init(parameters: UserSessionsOverviewCoordinatorParameters) { @@ -72,8 +66,6 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { guard let self = self else { return } MXLog.debug("[UserSessionsOverviewCoordinator] UserSessionsOverviewViewModel did complete with result: \(result).") switch result { - case .cancel: - self.completion?() case .showAllUnverifiedSessions: self.showAllUnverifiedSessions() case .showAllInactiveSessions: @@ -129,7 +121,7 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { guard let sessionInfo = service.getOtherSession(sessionId: sessionId) else { return } - delegate?.showUserSessionOverview(session: sessionInfo) + completion?(.openSessionDetails(session: sessionInfo)) } private func showAllOtherSessions() { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift index b5b408081..7f30f0fe3 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift @@ -18,10 +18,13 @@ import Foundation // MARK: - Coordinator +enum UserSessionsOverviewCoordinatorResult { + case openSessionDetails(session: UserSessionInfo) +} + // MARK: View model enum UserSessionsOverviewViewModelResult { - case cancel case showAllUnverifiedSessions case showAllInactiveSessions case verifyCurrentSession From 1aa718aa2b2b1d835fb073e56b3f7b32184e60c7 Mon Sep 17 00:00:00 2001 From: Doug Date: Thu, 22 Sep 2022 09:54:54 +0100 Subject: [PATCH 037/104] Disable codecov/patch. --- changelog.d/pr-6757.build | 1 + codecov.yml | 9 +-------- 2 files changed, 2 insertions(+), 8 deletions(-) create mode 100644 changelog.d/pr-6757.build diff --git a/changelog.d/pr-6757.build b/changelog.d/pr-6757.build new file mode 100644 index 000000000..e84ee6f0c --- /dev/null +++ b/changelog.d/pr-6757.build @@ -0,0 +1 @@ +Fix crash when scrolling chat list diff --git a/codecov.yml b/codecov.yml index 8af06f857..64701d167 100644 --- a/codecov.yml +++ b/codecov.yml @@ -6,14 +6,7 @@ coverage: # project coverage decrease by more than 1%: target: auto threshold: 1% - patch: - default: - # Be tolerant on slight code coverage diff on PRs to limit - # noisy red coverage status on github PRs. - # Note: The coverage stats are still uploaded - # to codecov so that PR reviewers can see uncovered lines - target: auto - threshold: 1% + patch: false ignore: - "Riot/Generated" # ignore the folder and all its contents \ No newline at end of file From 2cf7e7ebab470fe131eafbc3b4fe079dedbb2ab5 Mon Sep 17 00:00:00 2001 From: aringenbach Date: Fri, 23 Sep 2022 15:06:40 +0200 Subject: [PATCH 038/104] Fix render of links with both characters requiring percent encoding and markdown-like syntax --- .../EventFormatter/MarkdownToHTMLRenderer.swift | 5 +++-- RiotTests/MarkdownToHTMLRendererTests.swift | 14 ++++++++++++++ changelog.d/6748.bugfix | 1 + 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 changelog.d/6748.bugfix diff --git a/Riot/Modules/MatrixKit/Utils/EventFormatter/MarkdownToHTMLRenderer.swift b/Riot/Modules/MatrixKit/Utils/EventFormatter/MarkdownToHTMLRenderer.swift index 0800f4307..8c697dc6c 100644 --- a/Riot/Modules/MatrixKit/Utils/EventFormatter/MarkdownToHTMLRenderer.swift +++ b/Riot/Modules/MatrixKit/Utils/EventFormatter/MarkdownToHTMLRenderer.swift @@ -166,10 +166,11 @@ private extension CMarkNode { private extension String { /// Returns array of URLs detected inside the String. var containedUrls: [NSTextCheckingResult] { - guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else { + guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue), + let percentEncoded = self.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { return [] } - return detector.matches(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count)) + return detector.matches(in: percentEncoded, options: [], range: NSRange(location: 0, length: percentEncoded.utf16.count)) } } diff --git a/RiotTests/MarkdownToHTMLRendererTests.swift b/RiotTests/MarkdownToHTMLRendererTests.swift index 58626c1e3..918b4d494 100644 --- a/RiotTests/MarkdownToHTMLRendererTests.swift +++ b/RiotTests/MarkdownToHTMLRendererTests.swift @@ -112,6 +112,20 @@ final class MarkdownToHTMLRendererTests: XCTestCase { testRenderHTML(input: input, expectedOutput: expectedOutput) } + func testRenderRepairedLinksWithCharactersRequiringPercentEncoding() { + let input = "Some link with special characters: " + + "https://matrix.to/#/#_oftc_#matrix-dev:matrix.org" + + " " + + "https://matrix.to/#/#?=+-_#_" + + "\n" + let expectedOutput = "

Some link with special characters: " + + "https://matrix.to/#/#_oftc_#matrix-dev:matrix.org" + + " " + + "https://matrix.to/#/#?=+-_#_

" + + "\n" + testRenderHTML(input: input, expectedOutput: expectedOutput) + } + /// Test links inside codeblocks. func testRenderLinksInCodeblock() { let input = "```" diff --git a/changelog.d/6748.bugfix b/changelog.d/6748.bugfix new file mode 100644 index 000000000..0c9d08465 --- /dev/null +++ b/changelog.d/6748.bugfix @@ -0,0 +1 @@ +Fix render of links with both characters requiring percent encoding and markdown-like syntax From b75269cfa56975689971c2b2462090d369818aec Mon Sep 17 00:00:00 2001 From: oksya8and8 Date: Sun, 25 Sep 2022 22:52:13 +0000 Subject: [PATCH 039/104] Translated using Weblate (Japanese) Currently translated at 69.0% (1518 of 2198 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ja/ --- Riot/Assets/ja.lproj/Vector.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Assets/ja.lproj/Vector.strings b/Riot/Assets/ja.lproj/Vector.strings index ddd2b31e4..f602ab757 100644 --- a/Riot/Assets/ja.lproj/Vector.strings +++ b/Riot/Assets/ja.lproj/Vector.strings @@ -707,7 +707,7 @@ "settings_key_backup_info_version" = "鍵のバックアップのバージョン:%@"; "settings_key_backup_info_none" = "あなたの鍵は、このセッションからバックアップされていません。"; "settings_key_backup_info_checking" = "確認しています…"; -"settings_add_3pid_password_message" = "続けるにはパスワードを入力してください"; +"settings_add_3pid_password_message" = "続行するには、パスワードを入力してください"; "settings_add_3pid_invalid_password_message" = "無効な認証情報"; "settings_add_3pid_password_title_email" = "メールアドレスを追加"; "settings_integrations_allow_description" = "インテグレーションマネージャー(%@)を使用して、ボット、ブリッジ、ウィジェット、ステッカーパックを管理します。\n\n設定データを受け取り、お客様に代わってウィジェットの変更、ルーム招待の送信、権限の設定を行うことができます。"; From bd4bb7da58c9a399245a2be0e653863b858e41df Mon Sep 17 00:00:00 2001 From: Kaede Date: Sun, 25 Sep 2022 22:50:12 +0000 Subject: [PATCH 040/104] Translated using Weblate (Japanese) Currently translated at 69.0% (1518 of 2198 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ja/ --- Riot/Assets/ja.lproj/Vector.strings | 140 ++++++++++++++++++++++++---- 1 file changed, 124 insertions(+), 16 deletions(-) diff --git a/Riot/Assets/ja.lproj/Vector.strings b/Riot/Assets/ja.lproj/Vector.strings index f602ab757..8b337a5ab 100644 --- a/Riot/Assets/ja.lproj/Vector.strings +++ b/Riot/Assets/ja.lproj/Vector.strings @@ -142,8 +142,8 @@ "contacts_address_book_matrix_users_toggle" = "Matrix利用者のみ"; "contacts_address_book_no_contact" = "端末内電話帳に連絡先がありません"; "contacts_address_book_permission_required" = "端末内電話帳へのアクセス権限が必要です"; -"contacts_user_directory_section" = "利用者一覧"; -"contacts_user_directory_offline_section" = "利用者一覧(オフライン)"; +"contacts_user_directory_section" = "ユーザー一覧"; +"contacts_user_directory_offline_section" = "ユーザー一覧 (オフライン)"; // Chat participants "room_participants_title" = "参加者"; "room_participants_add_participant" = "参加者を追加"; @@ -172,7 +172,7 @@ "room_participants_action_section_devices" = "セッション一覧"; "room_participants_action_section_other" = "オプション"; "room_participants_action_invite" = "招待"; -"room_participants_action_leave" = "このルームを出る"; +"room_participants_action_leave" = "このルームを退出"; "room_participants_action_remove" = "このルームから削除"; "room_participants_action_ban" = "このルームからブロック"; "room_participants_action_unban" = "ブロックを解除"; @@ -208,7 +208,7 @@ "room_delete_unsent_messages" = "未送信の文を削除"; "room_event_action_copy" = "コピー"; "room_event_action_quote" = "引用"; -"room_event_action_redact" = "移動して削除"; +"room_event_action_redact" = "削除"; "room_event_action_more" = "さらに"; "room_event_action_share" = "共有"; "room_event_action_permalink" = "メッセージへのリンクをコピー"; @@ -332,7 +332,7 @@ "room_details_low_priority_tag" = "低優先度"; "room_details_mute_notifs" = "発言があっても通知しない"; "room_details_direct_chat" = "対話"; -"room_details_access_section" = "ルームにアクセスできる人"; +"room_details_access_section" = "このルームにアクセスできる人は?"; "room_details_access_section_invited_only" = "招待された人のみ"; "room_details_access_section_anyone_apart_from_guest" = "ルームのリンクを知っている人なら誰でも(ゲストユーザーを除く)"; "room_details_access_section_anyone" = "ルームのリンクを知っている人なら誰でも(ゲストユーザーを含む)"; @@ -565,12 +565,12 @@ "room_info_list_one_member" = "1人のメンバー"; "create_room_placeholder_address" = "#testroom:matrix.org"; -"create_room_section_header_address" = "ルームのアドレス"; -"create_room_show_in_directory" = "このルームを一覧に掲載"; +"create_room_section_header_address" = "アドレス"; +"create_room_show_in_directory" = "ルーム一覧に掲載"; "create_room_section_footer_type" = "非公開のルームは、ルームに招待された人のみ参加できます。"; "create_room_type_public" = "公開ルーム (誰でも参加可能)"; "create_room_type_private" = "非公開ルーム (招待者のみ参加可能)"; -"create_room_section_header_type" = "ルームの種類"; +"create_room_section_header_type" = "アクセスできる人"; "create_room_section_footer_encryption" = "暗号化はあとから無効にすることはできません。"; "create_room_section_header_encryption" = "ルームの暗号化"; "create_room_placeholder_topic" = "トピック"; @@ -674,7 +674,7 @@ "room_details_access_section_directory_toggle_for_dm" = "ルーム一覧に掲載"; "room_details_access_section_anyone_apart_from_guest_for_dm" = "リンクを知っている人なら誰でも(ゲストユーザーを除く)"; "room_details_access_section_anyone_for_dm" = "リンクを知っている人なら誰でも(ゲストユーザーを含む)"; -"room_details_access_section_for_dm" = "誰がアクセスできますか?"; +"room_details_access_section_for_dm" = "これにアクセスできる人は?"; "room_details_photo_for_dm" = "写真"; "room_details_integrations" = "インテグレーション"; "room_details_search" = "ルーム内検索"; @@ -735,7 +735,7 @@ "room_participants_action_security_status_warning" = "警告"; "room_participants_action_security_status_complete_security" = "セキュリティーを確認"; "room_participants_action_security_status_verify" = "認証"; -"room_participants_action_security_status_verified" = "認証済"; +"room_participants_action_security_status_verified" = "検証済み"; "room_participants_action_section_security" = "セキュリティー"; "room_participants_start_new_chat_error_using_user_email_without_identity_server" = "IDサーバーが設定されていないため、メールアドレスを使って連絡先とチャットを開始することができません。"; "room_participants_filter_room_members_for_dm" = "メンバーを検索"; @@ -870,7 +870,7 @@ "user_verification_session_details_additional_information_untrusted_other_user" = "ユーザーがこのセッションを信頼するまでは、セッションとの間で送受信されるメッセージには警告が表示されます。また、手動で認証することもできます。"; "user_verification_session_details_information_untrusted_other_user" = " 新しいセッションを使ってサインインしました:"; "user_verification_session_details_information_untrusted_current_user" = "このセッションを認証することで、信頼できるものとしてマークし、暗号化されたメッセージへのアクセスを許可します。"; -"user_verification_session_details_information_trusted_other_user_part2" = " 認証済:"; +"user_verification_session_details_information_trusted_other_user_part2" = " 検証済み:"; "user_verification_session_details_information_trusted_other_user_part1" = "このセッションは安全なものとして信頼されています。なぜなら "; "user_verification_session_details_information_trusted_current_user" = "このセッションは、認証されたため安全なものとして信頼されています。"; "user_verification_session_details_untrusted_title" = "信頼されていません"; @@ -928,7 +928,7 @@ "key_verification_incoming_request_incoming_alert_message" = "%@は認証を要求しています"; "key_verification_tile_conclusion_warning_title" = "信頼されていないサインイン"; -"key_verification_tile_conclusion_done_title" = "認証済"; +"key_verification_tile_conclusion_done_title" = "検証済み"; "key_verification_tile_request_incoming_approval_decline" = "却下"; "key_verification_tile_request_incoming_approval_accept" = "承認"; "key_verification_tile_request_status_accepted" = "あなたは承認しました"; @@ -1283,7 +1283,7 @@ "room_event_encryption_info_device_id" = "ID\n"; "room_event_encryption_info_device_verification" = "認証\n"; "room_event_encryption_info_device_fingerprint" = "Ed25519 fingerprint\n"; -"room_event_encryption_info_device_verified" = "認証済"; +"room_event_encryption_info_device_verified" = "検証済み"; "room_event_encryption_info_device_not_verified" = "認証されていない"; "room_event_encryption_info_device_blocked" = "ブラックリストに載せた"; "room_event_encryption_info_verify" = "認証しています…"; @@ -1512,7 +1512,7 @@ "notice_room_invite_by_you" = "%@を招待しました"; "notice_room_invite_you" = "%@があなたを招待しました"; "notice_room_join_by_you" = "参加しました"; -"notice_room_leave_by_you" = "退出しました"; +"notice_room_leave_by_you" = "あなたが退出しました"; "notice_room_kick_by_you" = "%@をキックしました"; "notice_room_unban_by_you" = "%@のブロックを解除しました"; "notice_room_ban_by_you" = "%@をブロックしました"; @@ -1587,7 +1587,7 @@ "biometrics_usage_reason" = "アプリを開くには認証が必要です"; "settings_sending_media" = "画像と動画の送信"; "invite_friends_share_text" = "%@ での連絡先: %@"; -"side_menu_action_invite_friends" = "招待する"; +"side_menu_action_invite_friends" = "友だちを招待する"; "call_more_actions_change_audio_device" = "オーディオデバイスを変更"; "call_more_actions_dialpad" = "ダイヤルパッド"; "onboarding_splash_login_button_title" = "既にアカウントを持っています"; @@ -1656,7 +1656,7 @@ "space_topic" = "説明文"; "spaces_creation_cancel_message" = "進捗状況は失われます。"; "spaces_creation_cancel_title" = "スペースの作成を停止しますか?"; -"create_room_section_footer_type_private" = "招待した人のみが検索や参加できます。"; +"create_room_section_footer_type_private" = "招待した人のみが検索・参加できます。"; // MARK: - Searchable Directory View Controller @@ -1668,3 +1668,111 @@ // MARK: Sign out warning "sign_out_existing_key_backup_alert_title" = "サインアウトしてよろしいですか?"; +"find_your_contacts_message" = "%@ であなたの連絡先を表示し、知人とのチャットを素早く始めます。"; +"find_your_contacts_footer" = "この設定はいつでも無効にできます"; +"find_your_contacts_button_title" = "連絡先を検索する"; +"find_your_contacts_title" = "連絡先をリストアップする"; + +// Mark: - Room avatar view + +"room_avatar_view_accessibility_label" = "アバター"; + +// MARK: - Invite friends + +"invite_friends_action" = "友だちを %@ に招待する"; +"call_transfer_error_title" = "エラー"; +"home_context_menu_mark_as_read" = "既読にする"; +"home_context_menu_normal_priority" = "通常優先度"; +"home_context_menu_low_priority" = "低優先度"; +"home_context_menu_unmute" = "ミュート解除"; +"home_context_menu_mute" = "ミュート"; + +// MARK: - Call Transfer +"call_transfer_title" = "転送"; +"room_info_back_button_title" = "ルーム情報"; +"create_room_processing" = "ルーム作成中"; +"call_transfer_users" = "ユーザー"; +"home_context_menu_notifications" = "通知"; +"home_context_menu_make_dm" = "連絡先に移動"; +"home_context_menu_make_room" = "ルームに移動"; +"leave_space_title" = "%@ を退出"; +"room_participants_leave_success" = "ルームを退出しました"; +"room_participants_leave_processing" = "退出中"; +"event_formatter_group_call_leave" = "退出"; +"home_context_menu_leave" = "退出"; + +// Mark: Leave space + +"leave_space_action" = "スペースを退出"; +"leave_space_selection_title" = "ルームを選択"; +"create_room_section_footer_type_restricted" = "誰でもスペース名で検索・参加できます。"; +"create_room_suggest_room" = "スペースメンバーにおすすめ"; +"create_room_show_in_directory_footer" = "他の人が検索・参加できるようになります。"; +"create_room_promotion_header" = "PR"; +"searchable_directory_search_placeholder" = "名前または ID"; +"room_suggestion_settings_screen_title" = "スペースにおすすめのルームを作成"; +"room_suggestion_settings_screen_message" = "おすすめのルームは、スペースメンバーに参加を推奨するものとして PR されます。"; + +// Room suggestion Settings +"room_suggestion_settings_screen_nav_title" = "おすすめのルーム"; +"room_details_promote_room_suggest_title" = "スペースメンバーへのおすすめ"; +"settings_default" = "デフォルトの通知"; +"pin_protection_reset_alert_action_reset" = "リセット"; +"authentication_recaptcha_title" = "あなたは人間ですか?"; +"authentication_verify_msisdn_waiting_button" = "コードを再送信"; +"authentication_choose_password_submit_button" = "パスワードをリセット"; +"authentication_choose_password_signout_all_devices" = "全ての端末からサインアウト"; +"authentication_choose_password_text_field_placeholder" = "新しいパスワード"; +"authentication_terms_title" = "プライバシーポリシー"; +"authentication_verify_msisdn_text_field_placeholder" = "電話番号"; +"password_validation_error_contain_symbol" = "記号を含める"; +"password_validation_error_contain_number" = "数字を含める"; +"password_validation_error_contain_uppercase_letter" = "大文字を含める"; +"password_validation_error_contain_lowercase_letter" = "小文字を含める"; +/* The placeholder will show a number */ +"password_validation_error_max_length" = "%d 文字以下"; +/* The placeholder will show a number */ +"password_validation_error_min_length" = "%d 文字以上"; + +// MARK: Password Validation +"password_validation_info_header" = "以下の条件を満たすパスワードを設定してください:"; +"space_selector_empty_view_title" = "まだスペースがありません"; +"all_chats_empty_list_placeholder_title" = "未読はありません"; +"all_chats_empty_unreads_placeholder_message" = "未読のメッセージがある場合は、ここに表示されます。"; +"room_notifs_settings_account_settings" = "アカウント設定"; +"room_access_settings_screen_upgrade_alert_upgrading" = "ルームをアップグレードしています"; +"room_access_settings_screen_upgrade_alert_upgrade_button" = "アップグレード"; +"room_access_settings_screen_edit_spaces" = "スペースを編集"; +"room_access_settings_screen_upgrade_required" = "アップグレードが必要"; +"room_access_settings_screen_upgrade_alert_title" = "ルームをアップグレード"; +"room_access_settings_screen_public_message" = "誰でも検索・参加できます。"; +"room_access_settings_screen_private_message" = "招待された人だけが検索・参加できます。"; +"room_access_settings_screen_message" = "誰が %@ を検索・参加できるか選択してください。"; +"space_settings_access_section" = "このスペースにアクセスできる人は?"; +"room_access_settings_screen_title" = "このルームにアクセスできる人は?"; +"room_notifs_settings_none" = "なし"; + +// Room Notification Settings +"room_notifs_settings_notify_me_for" = "以下の場合に通知"; +"room_details_access_row_title" = "アクセス"; +"room_details_notifs" = "通知"; +"location_sharing_invalid_power_level_title" = "現在地の共有に必要な権限がありません"; +"settings_labs_enable_live_location_sharing" = "現在地の共有 - 現在地を共有します (開発中の機能、位置情報が一時的にルームの履歴に残ります)"; +"event_formatter_message_deleted" = "削除済みのメッセージ"; +"home_context_menu_unfavourite" = "お気に入りから削除"; +"home_context_menu_favourite" = "お気に入り"; +"all_chats_user_menu_settings" = "ユーザー設定"; +"all_chats_edit_layout_show_filters" = "フィルターを表示"; +"all_chats_edit_layout_show_recents" = "最近使用したものを表示"; +"all_chats_edit_layout_alphabetical_order" = "A-Z で並び替え"; +"all_chats_edit_layout_activity_order" = "アクティビティで並び替え"; +"space_selector_create_space" = "スペースを作成"; +"space_selector_empty_view_information" = "スペースは、ルームや連絡先をグループ化する方法です。以下からスペースを作成できます。"; +"all_chats_all_filter" = "全て"; +"all_chats_edit_layout_recents" = "最近"; +"all_chats_edit_layout_unreads" = "未読"; +"all_chats_section_title" = "チャット"; + +// Mark: - All Chats + +"all_chats_title" = "全てのチャット"; From 20d5f66d2eb5929baf41a06f44b13be4bec1dc17 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Mon, 26 Sep 2022 10:23:31 +0200 Subject: [PATCH 041/104] Include concrete device model in user agent (PSG-754) Fixes: #6742 --- Config/CommonConfiguration.swift | 69 ++++++++++++++++++- .../xcshareddata/swiftpm/Package.resolved | 9 +++ Riot/target.yml | 1 + RiotNSE/target.yml | 3 + RiotShareExtension/target.yml | 3 + SiriIntents/target.yml | 1 + changelog.d/6742.change | 1 + project.yml | 4 ++ 8 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 changelog.d/6742.change diff --git a/Config/CommonConfiguration.swift b/Config/CommonConfiguration.swift index 5937b4659..b459eb922 100644 --- a/Config/CommonConfiguration.swift +++ b/Config/CommonConfiguration.swift @@ -17,6 +17,10 @@ import Foundation import MatrixSDK +#if !os(OSX) +import DeviceKit +#endif + /// CommonConfiguration is the central point to setup settings for MatrixSDK, MatrixKit and common configurations for all targets. class CommonConfiguration: NSObject, Configurable { @@ -66,8 +70,13 @@ class CommonConfiguration: NSObject, Configurable { // Disable identicon use sdkOptions.disableIdenticonUseForUserAvatar = true + // Set up user agent + if let userAgent = makeASCIIUserAgent() { + sdkOptions.httpAdditionalHeaders = ["User-Agent": userAgent] + } + // Pass httpAdditionalHeaders to the SDK - sdkOptions.httpAdditionalHeaders = BuildSettings.httpAdditionalHeaders + sdkOptions.httpAdditionalHeaders = (sdkOptions.httpAdditionalHeaders ?? [:]).merging(BuildSettings.httpAdditionalHeaders, uniquingKeysWith: { _, value in value }) // Disable key backup on common sdkOptions.enableKeyBackupWhenStartingMXCrypto = false @@ -82,6 +91,64 @@ class CommonConfiguration: NSObject, Configurable { MXKeyProvider.sharedInstance().delegate = EncryptionKeyManager.shared } + private func makeASCIIUserAgent() -> String? { + guard var userAgent = makeUserAgent() else { + return nil + } + if !userAgent.canBeConverted(to: .ascii) { + let mutableUserAgent = NSMutableString(string: userAgent) + if CFStringTransform(mutableUserAgent, nil, "Any-Latin; Latin-ASCII; [:^ASCII:] Remove" as CFString, false) { + userAgent = mutableUserAgent as String + } + } + return userAgent + } + + private func makeUserAgent() -> String? { + let clientName = Bundle.main.object(forInfoDictionaryKey: kCFBundleExecutableKey as String) as? String ?? Bundle.main.object(forInfoDictionaryKey: kCFBundleIdentifierKey as String) as? String ?? "unknown" + let clientVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String ?? "unknown" + + #if os(iOS) + return String( + format: "%@/%@ (%@; iOS %@; Scale/%0.2f)", + clientName, + clientVersion, + Device.current.safeDescription, + UIDevice.current.systemVersion, + UIScreen.main.scale) + #else + #if os(tvOS) + return String( + format: "%@/%@ (%@; tvOS %@; Scale/%0.2f)", + clientName, + clientVersion, + Device.current.safeDescription, + UIDevice.current.systemVersion, + UIScreen.main.scale) + #else + #if os(watchOS) + return String( + format: "%@/%@ (%@; watchOS %@; Scale/%0.2f)", + clientName, + clientVersion, + Device.current.safeDescription, + WKInterfaceDevice.current.systemVersion, + WKInterfaceDevice.currentDevice.screenScale) + #else + #if os(OSX) + return String( + format: "%@/%@ (Mac; Mac OS X %@)", + clientName, + clientVersion, + NSProcessInfo.processInfo.operatingSystemVersionString) + #else + return nil + #endif + #endif + #endif + #endif + } + // MARK: - Per matrix session settings diff --git a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3d248ff51..7b45c9e98 100644 --- a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "devicekit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/devicekit/DeviceKit", + "state" : { + "revision" : "20e0991f3975916ab0f6d58db84d8bc64f883537", + "version" : "4.7.0" + } + }, { "identity" : "maplibre-gl-native-distribution", "kind" : "remoteSourceControl", diff --git a/Riot/target.yml b/Riot/target.yml index f2a1bb16b..1d6b61fbb 100644 --- a/Riot/target.yml +++ b/Riot/target.yml @@ -39,6 +39,7 @@ targets: - package: Mapbox - package: OrderedCollections - package: SwiftOGG + - package: DeviceKit configFiles: Debug: Debug.xcconfig diff --git a/RiotNSE/target.yml b/RiotNSE/target.yml index bbf8a6ab3..22b415154 100644 --- a/RiotNSE/target.yml +++ b/RiotNSE/target.yml @@ -31,6 +31,9 @@ targets: platform: iOS type: app-extension + dependencies: + - package: DeviceKit + configFiles: Debug: Debug.xcconfig Release: Release.xcconfig diff --git a/RiotShareExtension/target.yml b/RiotShareExtension/target.yml index 29dfe514d..a44714f33 100644 --- a/RiotShareExtension/target.yml +++ b/RiotShareExtension/target.yml @@ -31,6 +31,9 @@ targets: platform: iOS type: app-extension + dependencies: + - package: DeviceKit + configFiles: Debug: Debug.xcconfig Release: Release.xcconfig diff --git a/SiriIntents/target.yml b/SiriIntents/target.yml index ccce66a4a..d20501785 100644 --- a/SiriIntents/target.yml +++ b/SiriIntents/target.yml @@ -33,6 +33,7 @@ targets: dependencies: - sdk: Intents.framework + - package: DeviceKit configFiles: Debug: Debug.xcconfig diff --git a/changelog.d/6742.change b/changelog.d/6742.change new file mode 100644 index 000000000..38aab166a --- /dev/null +++ b/changelog.d/6742.change @@ -0,0 +1 @@ +Include concrete device model in user agent (PSG-754) diff --git a/project.yml b/project.yml index aa33c25d8..aa4d0f65b 100644 --- a/project.yml +++ b/project.yml @@ -51,3 +51,7 @@ packages: SwiftOGG: url: https://github.com/vector-im/swift-ogg branch: main + DeviceKit: + url: https://github.com/devicekit/DeviceKit + minVersion: 4.7.0 + maxVersion: 4.7.0 From 7c980949488b85f1f0dc8367efbfad2da865f20f Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Mon, 26 Sep 2022 12:02:54 +0200 Subject: [PATCH 042/104] Add entry in third_party_licences.html --- Riot/Assets/third_party_licenses.html | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Riot/Assets/third_party_licenses.html b/Riot/Assets/third_party_licenses.html index a9365018f..ebd5e6b11 100644 --- a/Riot/Assets/third_party_licenses.html +++ b/Riot/Assets/third_party_licenses.html @@ -1908,6 +1908,32 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+
  • + DeviceKit (https://github.com/devicekit/DeviceKit) +

    + MIT License +

    + Copyright (c) 2015 Dennis Weissmann +

    + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: +

    + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. +

    + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +

    +
  • From c93e9ad9e924544992d45647ba5a3c9fb7b2d53b Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Mon, 26 Sep 2022 12:04:14 +0200 Subject: [PATCH 043/104] Switch to majorVersion for pinning dependency --- project.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/project.yml b/project.yml index aa4d0f65b..90112cd1b 100644 --- a/project.yml +++ b/project.yml @@ -53,5 +53,4 @@ packages: branch: main DeviceKit: url: https://github.com/devicekit/DeviceKit - minVersion: 4.7.0 - maxVersion: 4.7.0 + majorVersion: 4.7.0 From 7b4da1ae972973337ad7cb8df7d79058e8401cf0 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Mon, 26 Sep 2022 12:07:19 +0200 Subject: [PATCH 044/104] Switch to #elseif --- Config/CommonConfiguration.swift | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Config/CommonConfiguration.swift b/Config/CommonConfiguration.swift index b459eb922..d2dc4464f 100644 --- a/Config/CommonConfiguration.swift +++ b/Config/CommonConfiguration.swift @@ -116,8 +116,7 @@ class CommonConfiguration: NSObject, Configurable { Device.current.safeDescription, UIDevice.current.systemVersion, UIScreen.main.scale) - #else - #if os(tvOS) + #elseif os(tvOS) return String( format: "%@/%@ (%@; tvOS %@; Scale/%0.2f)", clientName, @@ -125,8 +124,7 @@ class CommonConfiguration: NSObject, Configurable { Device.current.safeDescription, UIDevice.current.systemVersion, UIScreen.main.scale) - #else - #if os(watchOS) + #elseif os(watchOS) return String( format: "%@/%@ (%@; watchOS %@; Scale/%0.2f)", clientName, @@ -134,8 +132,7 @@ class CommonConfiguration: NSObject, Configurable { Device.current.safeDescription, WKInterfaceDevice.current.systemVersion, WKInterfaceDevice.currentDevice.screenScale) - #else - #if os(OSX) + #elseif os(OSX) return String( format: "%@/%@ (Mac; Mac OS X %@)", clientName, @@ -144,9 +141,6 @@ class CommonConfiguration: NSObject, Configurable { #else return nil #endif - #endif - #endif - #endif } From fd07f98cd30c1f9595a91d942a5f3a50c5b5216d Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Mon, 26 Sep 2022 12:17:47 +0200 Subject: [PATCH 045/104] Use AppInfo to get version string --- Config/CommonConfiguration.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Config/CommonConfiguration.swift b/Config/CommonConfiguration.swift index d2dc4464f..6c4cecdfe 100644 --- a/Config/CommonConfiguration.swift +++ b/Config/CommonConfiguration.swift @@ -106,7 +106,7 @@ class CommonConfiguration: NSObject, Configurable { private func makeUserAgent() -> String? { let clientName = Bundle.main.object(forInfoDictionaryKey: kCFBundleExecutableKey as String) as? String ?? Bundle.main.object(forInfoDictionaryKey: kCFBundleIdentifierKey as String) as? String ?? "unknown" - let clientVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String ?? "unknown" + let clientVersion = AppInfo.current.appVersion?.bundleShortVersion ?? "unknown" #if os(iOS) return String( From 75f99801ef28a8965db358e5eaf2bcbd473b7371 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Mon, 26 Sep 2022 14:56:15 +0300 Subject: [PATCH 046/104] Avoid redundant media reloads (#6763) * Avoid redundant reloads on media viewer * Add changelog --- .../Modules/Room/Attachements/MXKAttachmentsViewController.m | 5 +++++ changelog.d/6717.bugfix | 1 + 2 files changed, 6 insertions(+) create mode 100644 changelog.d/6717.bugfix diff --git a/Riot/Modules/Room/Attachements/MXKAttachmentsViewController.m b/Riot/Modules/Room/Attachements/MXKAttachmentsViewController.m index 37759b261..331121341 100644 --- a/Riot/Modules/Room/Attachements/MXKAttachmentsViewController.m +++ b/Riot/Modules/Room/Attachements/MXKAttachmentsViewController.m @@ -346,6 +346,11 @@ - (void)displayAttachments:(NSArray*)attachmentArray focusOn:(NSString*)eventId { + if ([attachmentArray isEqualToArray:attachments] && eventId.length == 0) + { + // neither the attachments nor the focus changed, can be ignored + return; + } NSString *currentAttachmentEventId = eventId; NSString *currentAttachmentOriginalFileName = nil; diff --git a/changelog.d/6717.bugfix b/changelog.d/6717.bugfix new file mode 100644 index 000000000..c3cc45ae3 --- /dev/null +++ b/changelog.d/6717.bugfix @@ -0,0 +1 @@ +Media: Avoid redundant reloads on media viewer. From 8680175a5ff696de97091cf0513f1d96d897450e Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Mon, 26 Sep 2022 14:05:08 +0200 Subject: [PATCH 047/104] Fetch display name from AppInfo, too --- Config/CommonConfiguration.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Config/CommonConfiguration.swift b/Config/CommonConfiguration.swift index 6c4cecdfe..6f9a7f555 100644 --- a/Config/CommonConfiguration.swift +++ b/Config/CommonConfiguration.swift @@ -105,8 +105,9 @@ class CommonConfiguration: NSObject, Configurable { } private func makeUserAgent() -> String? { - let clientName = Bundle.main.object(forInfoDictionaryKey: kCFBundleExecutableKey as String) as? String ?? Bundle.main.object(forInfoDictionaryKey: kCFBundleIdentifierKey as String) as? String ?? "unknown" - let clientVersion = AppInfo.current.appVersion?.bundleShortVersion ?? "unknown" + let appInfo = AppInfo.current + let clientName = appInfo.displayName + let clientVersion = appInfo.appVersion?.bundleShortVersion ?? "unknown" #if os(iOS) return String( From b86c2ca150b539b3a361e403ae5f64b3ff4b15cd Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Fri, 23 Sep 2022 17:16:18 +0300 Subject: [PATCH 048/104] Session overview screen --- .../UserSessionDetailsCoordinator.swift | 1 - .../MockUserSessionDetailsScreenState.swift | 2 +- .../UserSessionOverviewCoordinator.swift | 77 +++++++++++++++++++ .../MockUserSessionOverviewScreenState.swift | 69 +++++++++++++++++ .../Test/UI/UserSessionOverviewUITests.swift | 57 ++++++++++++++ .../UserSessionOverviewViewModelTests.swift | 56 ++++++++++++++ .../UserSessionOverviewModels.swift | 42 ++++++++++ .../UserSessionOverviewViewModel.swift | 53 +++++++++++++ ...UserSessionOverviewViewModelProtocol.swift | 23 ++++++ .../View/UserSessionOverview.swift | 73 ++++++++++++++++++ .../UserSessionOverviewDisclosureCell.swift | 45 +++++++++++ .../UserSessionsFlowCoordinator.swift | 26 +++++-- .../UserSessionsOverviewCoordinator.swift | 19 ++--- .../MockUserSessionsOverviewService.swift | 6 +- .../UserSessionsOverviewServiceProtocol.swift | 2 + .../UserSessionsOverviewModels.swift | 6 +- .../UserSessionsOverviewViewModel.swift | 12 ++- .../View/UserSessionListItem.swift | 5 +- 18 files changed, 545 insertions(+), 29 deletions(-) create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModelProtocol.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift index 378a53c6c..28b16168a 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift @@ -18,7 +18,6 @@ import SwiftUI import CommonKit struct UserSessionDetailsCoordinatorParameters { - let session: MXSession let userSessionInfo: UserSessionInfo } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift index 317b2d015..3f9a2e634 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift @@ -33,7 +33,7 @@ enum MockUserSessionDetailsScreenState: MockScreenState, CaseIterable { /// A list of screen state definitions static var allCases: [MockUserSessionDetailsScreenState] { // Each of the presence statuses - return [.allSections, sessionSectionOnly] + return [.allSections, .sessionSectionOnly] } /// Generate the view struct for the screen state. diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift new file mode 100644 index 000000000..eedacbf9a --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift @@ -0,0 +1,77 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI +import CommonKit + +struct UserSessionOverviewCoordinatorParameters { + let userSessionInfo: UserSessionInfo + let isCurrentSession: Bool +} + +final class UserSessionOverviewCoordinator: Coordinator, Presentable { + + // MARK: - Properties + + // MARK: Private + + private let parameters: UserSessionOverviewCoordinatorParameters + private let userSessionOverviewHostingController: UIViewController + private var userSessionOverviewViewModel: UserSessionOverviewViewModelProtocol + + private var indicatorPresenter: UserIndicatorTypePresenterProtocol + private var loadingIndicator: UserIndicator? + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + var completion: ((UserSessionOverviewCoordinatorResult) -> Void)? + + // MARK: - Setup + + init(parameters: UserSessionOverviewCoordinatorParameters) { + self.parameters = parameters + let viewModel = UserSessionOverviewViewModel(userSessionInfo: parameters.userSessionInfo, isCurrentSession: parameters.isCurrentSession) + let view = UserSessionOverview(viewModel: viewModel.context) + userSessionOverviewViewModel = viewModel + userSessionOverviewHostingController = VectorHostingController(rootView: view) + + indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: userSessionOverviewHostingController) + } + + // MARK: - Public + + func start() { + MXLog.debug("[UserSessionOverviewCoordinator] did start.") + userSessionOverviewViewModel.completion = { [weak self] result in + guard let self = self else { return } + MXLog.debug("[UserSessionOverviewCoordinator] UserSessionOverviewViewModel did complete with result: \(result).") + switch result { + case .verifyCurrentSession: + break // TODO + case let .showCurrentSessionDetails(sessionInfo: sessionInfo): + self.completion?(.openSessionDetails(session: sessionInfo)) + } + } + } + + func toPresentable() -> UIViewController { + return self.userSessionOverviewHostingController + } + + // MARK: - Private +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift new file mode 100644 index 000000000..a24ea52bb --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift @@ -0,0 +1,69 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import SwiftUI + +/// Using an enum for the screen allows you define the different state cases with +/// the relevant associated data for each case. +enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { + // A case for each state you want to represent + // with specific, minimal associated data that will allow you + // mock that screen. + case currentSession + case otherSession + + /// The associated screen + var screenType: Any.Type { + UserSessionOverview.self + } + + /// A list of screen state definitions + static var allCases: [MockUserSessionOverviewScreenState] { + // Each of the presence statuses + return [.currentSession, .otherSession] + } + + /// Generate the view struct for the screen state. + var screenView: ([Any], AnyView) { + let viewModel: UserSessionOverviewViewModel + switch self { + case .currentSession: + let currentSessionInfo = UserSessionInfo(sessionId: "session", + sessionName: "iOS", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + viewModel = UserSessionOverviewViewModel(userSessionInfo: currentSessionInfo, isCurrentSession: true) + case .otherSession: + let currentSessionInfo = UserSessionInfo(sessionId: "session", + sessionName: "Mac", + deviceType: .desktop, + isVerified: true, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + viewModel = UserSessionOverviewViewModel(userSessionInfo: currentSessionInfo, isCurrentSession: false) + } + + // can simulate service and viewModel actions here if needs be. + + return ( + [viewModel], + AnyView(UserSessionOverview(viewModel: viewModel.context)) + ) + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift new file mode 100644 index 000000000..5958a9a0a --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift @@ -0,0 +1,57 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +import RiotSwiftUI + +class UserSessionOverviewUITests: MockScreenTestCase { + func testUserSessionOverviewPresenceIdle() { + let presence = UserSessionOverviewPresence.idle + app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.presence(presence).title) + + let presenceText = app.staticTexts["presenceText"] + XCTAssert(presenceText.exists) + XCTAssertEqual(presenceText.label, presence.title) + } + + func testUserSessionOverviewPresenceOffline() { + let presence = UserSessionOverviewPresence.offline + app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.presence(presence).title) + + let presenceText = app.staticTexts["presenceText"] + XCTAssert(presenceText.exists) + XCTAssertEqual(presenceText.label, presence.title) + } + + func testUserSessionOverviewPresenceOnline() { + let presence = UserSessionOverviewPresence.online + app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.presence(presence).title) + + let presenceText = app.staticTexts["presenceText"] + XCTAssert(presenceText.exists) + XCTAssertEqual(presenceText.label, presence.title) + } + + func testUserSessionOverviewLongName() { + let name = "Somebody with a super long name we would like to test" + app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.longDisplayName(name).title) + + let displayNameText = app.staticTexts["displayNameText"] + XCTAssert(displayNameText.exists) + XCTAssertEqual(displayNameText.label, name) + } + +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift new file mode 100644 index 000000000..aac52cb0f --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift @@ -0,0 +1,56 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +import Combine + +@testable import RiotSwiftUI + +class UserSessionOverviewViewModelTests: XCTestCase { + private enum Constants { + static let presenceInitialValue: UserSessionOverviewPresence = .offline + static let displayName = "Alice" + } + var service: MockUserSessionOverviewService! + var viewModel: UserSessionOverviewViewModelProtocol! + var context: UserSessionOverviewViewModelType.Context! + var cancellables = Set() + override func setUpWithError() throws { + service = MockUserSessionOverviewService(displayName: Constants.displayName, presence: Constants.presenceInitialValue) + viewModel = UserSessionOverviewViewModel.makeUserSessionOverviewViewModel(userSessionOverviewService: service) + context = viewModel.context + } + + func testInitialState() { + XCTAssertEqual(context.viewState.displayName, Constants.displayName) + XCTAssertEqual(context.viewState.presence, Constants.presenceInitialValue) + } + + func testFirstPresenceReceived() throws { + let presencePublisher = context.$viewState.map(\.presence).removeDuplicates().collect(1).first() + XCTAssertEqual(try xcAwait(presencePublisher), [Constants.presenceInitialValue]) + } + + func testPresenceUpdatesReceived() throws { + let presencePublisher = context.$viewState.map(\.presence).removeDuplicates().collect(3).first() + let awaitDeferred = xcAwaitDeferred(presencePublisher) + let newPresenceValue1: UserSessionOverviewPresence = .online + let newPresenceValue2: UserSessionOverviewPresence = .idle + service.simulateUpdate(presence: newPresenceValue1) + service.simulateUpdate(presence: newPresenceValue2) + XCTAssertEqual(try awaitDeferred(), [Constants.presenceInitialValue, newPresenceValue1, newPresenceValue2]) + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift new file mode 100644 index 000000000..40284c91c --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift @@ -0,0 +1,42 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +// MARK: - Coordinator + +enum UserSessionOverviewCoordinatorResult { + case openSessionDetails(session: UserSessionInfo) +} + +// MARK: View model + +enum UserSessionOverviewViewModelResult { + case showCurrentSessionDetails(sessionInfo: UserSessionInfo) + case verifyCurrentSession +} + +// MARK: View + +struct UserSessionOverviewViewState: BindableState { + let cardViewData: UserSessionCardViewData + let isCurrentSession: Bool +} + +enum UserSessionOverviewViewAction { + case verifyCurrentSession + case viewSessionDetails +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift new file mode 100644 index 000000000..3eb381e02 --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift @@ -0,0 +1,53 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +typealias UserSessionOverviewViewModelType = StateStoreViewModel + +class UserSessionOverviewViewModel: UserSessionOverviewViewModelType, UserSessionOverviewViewModelProtocol { + + // MARK: - Properties + + // MARK: Private + private let userSessionInfo: UserSessionInfo + // MARK: Public + + var completion: ((UserSessionOverviewViewModelResult) -> Void)? + + // MARK: - Setup + + init(userSessionInfo: UserSessionInfo, isCurrentSession: Bool) { + self.userSessionInfo = userSessionInfo + + let cardViewData = UserSessionCardViewData(userSessionInfo: userSessionInfo, isCurrentSessionDisplayMode: isCurrentSession) + let state = UserSessionOverviewViewState(cardViewData: cardViewData, isCurrentSession: isCurrentSession) + super.init(initialViewState: state) + } + + // MARK: - Public + + override func process(viewAction: UserSessionOverviewViewAction) { + switch viewAction { + case .verifyCurrentSession: + completion?(.verifyCurrentSession) + case .viewSessionDetails: + completion?(.showCurrentSessionDetails(sessionInfo: userSessionInfo)) + } + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModelProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModelProtocol.swift new file mode 100644 index 000000000..014dcd8a7 --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModelProtocol.swift @@ -0,0 +1,23 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +protocol UserSessionOverviewViewModelProtocol { + + var completion: ((UserSessionOverviewViewModelResult) -> Void)? { get set } + var context: UserSessionOverviewViewModelType.Context { get } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift new file mode 100644 index 000000000..9e006289b --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift @@ -0,0 +1,73 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct UserSessionOverview: View { + + // MARK: - Properties + + // MARK: Private + + @Environment(\.theme) private var theme: ThemeSwiftUI + + // MARK: Public + + @ObservedObject var viewModel: UserSessionOverviewViewModel.Context + + var body: some View { + ScrollView { + UserSessionCardView(viewData: viewModel.viewState.cardViewData, + onVerifyAction: { _ in + viewModel.send(viewAction: .verifyCurrentSession) + }, + onViewDetailsAction: { _ in + viewModel.send(viewAction: .viewSessionDetails) + }) + .padding(16) + SwiftUI.Section { + UserSessionOverviewDisclosureCell(title: "Session details", onBackgroundTap: { + viewModel.send(viewAction: .viewSessionDetails) + }) + } + } + .background(theme.colors.system.ignoresSafeArea()) + .frame(maxHeight: .infinity) + .navigationTitle(viewModel.viewState.isCurrentSession ? "Current session" : "Session") + } +} + +struct SeparatorLine: View { + + @Environment(\.theme) private var theme: ThemeSwiftUI + + var body: some View { + Rectangle() + .fill(theme.colors.quinaryContent) + .frame(maxWidth: .infinity, alignment: .trailing) + .frame(height: 1.0) + } +} + +// MARK: - Previews + +struct UserSessionOverview_Previews: PreviewProvider { + static let stateRenderer = MockUserSessionOverviewScreenState.stateRenderer + static var previews: some View { + stateRenderer.screenGroup(addNavigation: true).theme(.light).preferredColorScheme(.light) + stateRenderer.screenGroup(addNavigation: true).theme(.dark).preferredColorScheme(.dark) + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift new file mode 100644 index 000000000..64e66b5ee --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift @@ -0,0 +1,45 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct UserSessionOverviewDisclosureCell: View { + + @Environment(\.theme) private var theme: ThemeSwiftUI + + let title: String + var onBackgroundTap: (() -> (Void))? = nil + + var body: some View { + VStack(spacing: 0) { + SeparatorLine() + HStack() { + Text(title) + .font(theme.fonts.body) + .foregroundColor(theme.colors.primaryContent) + .frame(maxWidth: .infinity, alignment: .leading) + Image(Asset.Images.chevron.name) + } + .padding(.vertical, 12) + .padding(.horizontal, 16) + SeparatorLine() + } + .background(theme.colors.background) + .onTapGesture { + onBackgroundTap?() + } + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift index b4835c8e2..db751a80f 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift @@ -48,7 +48,6 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { private func pushScreen(with coordinator: Coordinator & Presentable) { add(childCoordinator: coordinator) - self.navigationRouter.push(coordinator, animated: true, popCompletion: { [weak self] in self?.remove(childCoordinator: coordinator) }) @@ -63,8 +62,8 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { coordinator.completion = { [weak self] result in guard let self = self else { return } switch result { - case let .openSessionDetails(session: session): - self.openSessionDetails(session: session) + case let .openSessionOverview(session: session, isCurrentSession: isCurrentSession): + self.openSessionOverview(session: session, isCurrentSession: isCurrentSession) } } return coordinator @@ -76,12 +75,27 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { } private func createUserSessionDetailsCoordinator(session: UserSessionInfo) -> UserSessionDetailsCoordinator { - let parameters = UserSessionDetailsCoordinatorParameters( - session: parameters.session, - userSessionInfo: session) + let parameters = UserSessionDetailsCoordinatorParameters(userSessionInfo: session) return UserSessionDetailsCoordinator(parameters: parameters) } + private func openSessionOverview(session: UserSessionInfo, isCurrentSession: Bool) { + let coordinator = createUserSessionOverviewCoordinator(session: session, isCurrentSession: isCurrentSession) + coordinator.completion = { [weak self] result in + guard let self = self else { return } + switch result { + case let .openSessionDetails(session: session): + self.openSessionDetails(session: session) + } + } + pushScreen(with: coordinator) + } + + private func createUserSessionOverviewCoordinator(session: UserSessionInfo, isCurrentSession: Bool) -> UserSessionOverviewCoordinator { + let parameters = UserSessionOverviewCoordinatorParameters(userSessionInfo: session, isCurrentSession: isCurrentSession) + return UserSessionOverviewCoordinator(parameters: parameters) + } + // MARK: - Public func start() { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift index f8726ba5c..b98f18fae 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift @@ -72,12 +72,12 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { self.showAllInactiveSessions() case .verifyCurrentSession: self.startVerifyCurrentSession() - case .showCurrentSessionDetails: - self.showCurrentSessionDetails() + case let .showCurrentSessionOverview(sessionInfo): + self.showCurrentSessionOverview(sessionInfo: sessionInfo) case .showAllOtherSessions: self.showAllOtherSessions() - case .showUserSessionDetails(let sessionId): - self.showUserSessionDetails(sessionId: sessionId) + case let .showUserSessionOverview(sessionInfo): + self.showUserSessionOverview(sessionInfo: sessionInfo) } } } @@ -113,15 +113,12 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { // TODO } - private func showCurrentSessionDetails() { - // TODO + private func showCurrentSessionOverview(sessionInfo: UserSessionInfo) { + completion?(.openSessionOverview(session: sessionInfo, isCurrentSession: true)) } - private func showUserSessionDetails(sessionId: String) { - guard let sessionInfo = service.getOtherSession(sessionId: sessionId) else { - return - } - completion?(.openSessionDetails(session: sessionInfo)) + private func showUserSessionOverview(sessionInfo: UserSessionInfo) { + completion?(.openSessionOverview(session: sessionInfo, isCurrentSession: false)) } private func showAllOtherSessions() { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift index f0aa43861..d0621cd9c 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift @@ -17,13 +17,17 @@ import Foundation class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { - + var lastOverviewData: UserSessionsOverviewData func fetchUserSessionsOverviewData(completion: @escaping (Result) -> Void) { completion(.success(self.lastOverviewData)) } + func getOtherSession(sessionId: String) -> UserSessionInfo? { + nil + } + init() { let currentSessionInfo = UserSessionInfo(sessionId: "alice", sessionName: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: nil) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift index e7774be69..dec3b06df 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift @@ -29,4 +29,6 @@ protocol UserSessionsOverviewServiceProtocol { var lastOverviewData: UserSessionsOverviewData { get } func fetchUserSessionsOverviewData(completion: @escaping (Result) -> Void) -> Void + + func getOtherSession(sessionId: String) -> UserSessionInfo? } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift index 7f30f0fe3..dd27e0c63 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift @@ -19,7 +19,7 @@ import Foundation // MARK: - Coordinator enum UserSessionsOverviewCoordinatorResult { - case openSessionDetails(session: UserSessionInfo) + case openSessionOverview(session: UserSessionInfo, isCurrentSession: Bool) } // MARK: View model @@ -28,9 +28,9 @@ enum UserSessionsOverviewViewModelResult { case showAllUnverifiedSessions case showAllInactiveSessions case verifyCurrentSession - case showCurrentSessionDetails + case showCurrentSessionOverview(sessionInfo: UserSessionInfo) case showAllOtherSessions - case showUserSessionDetails(_ sessionId: String) + case showUserSessionOverview(sessionInfo: UserSessionInfo) } // MARK: View diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift index fcc5d73f4..b3e185d86 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift @@ -53,7 +53,11 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess case .verifyCurrentSession: self.completion?(.verifyCurrentSession) case .viewCurrentSessionDetails: - self.completion?(.showCurrentSessionDetails) + guard let currentSessionInfo = userSessionsOverviewService.lastOverviewData.currentSessionInfo else { + assertionFailure("currentSessionInfo should be present") + return + } + self.completion?(.showCurrentSessionOverview(sessionInfo: currentSessionInfo)) case .viewAllUnverifiedSessions: self.completion?(.showAllUnverifiedSessions) case .viewAllInactiveSessions: @@ -61,7 +65,11 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess case .viewAllOtherSessions: self.completion?(.showAllOtherSessions) case .tapUserSession(let sessionId): - self.completion?(.showUserSessionDetails(sessionId)) + guard let sessionInfo = userSessionsOverviewService.getOtherSession(sessionId: sessionId) else { + assertionFailure("missing session info") + return + } + self.completion?(.showUserSessionOverview(sessionInfo: sessionInfo)) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift index ae1b65893..7342764c4 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift @@ -64,10 +64,7 @@ struct UserSessionListItem: View { // Separator // Note: Separator leading is matching the text leading, we could use alignment guide in the future - Rectangle() - .fill(theme.colors.quinaryContent) - .frame(maxWidth: .infinity, alignment: .trailing) - .frame(height: 1.0) + SeparatorLine() .padding(.leading, LayoutConstants.horizontalPadding + LayoutConstants.avatarRightMargin + LayoutConstants.avatarWidth) } .padding(.top, LayoutConstants.verticalPadding) From b04a4e71b436738ba1f05476cecd6fa0db73000b Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Mon, 26 Sep 2022 13:37:21 +0300 Subject: [PATCH 049/104] Added translation strings --- Riot/Assets/en.lproj/Vector.strings | 4 ++++ Riot/Generated/Strings.swift | 12 ++++++++++++ .../View/UserSessionOverview.swift | 6 ++++-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index dedbeb99e..7165bdcd2 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2391,6 +2391,10 @@ To enable access, tap Settings> Location and select Always"; "user_session_details_session_id" = "Session ID"; "user_session_details_session_section_footer" = "Copy any data by tapping on it and holding it down."; "user_session_details_device_ip_address" = "IP address"; + +"user_session_overview_current_session_title" = "Current session"; +"user_session_overview_session_title" = "Session"; +"user_session_overview_session_details_button_title" = "Session details"; // MARK: - MatrixKit diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 346129042..00249e843 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -8507,6 +8507,18 @@ public class VectorL10n: NSObject { public static func userSessionName(_ p1: String, _ p2: String) -> String { return VectorL10n.tr("Vector", "user_session_name", p1, p2) } + /// Current session + public static var userSessionOverviewCurrentSessionTitle: String { + return VectorL10n.tr("Vector", "user_session_overview_current_session_title") + } + /// Session details + public static var userSessionOverviewSessionDetailsButtonTitle: String { + return VectorL10n.tr("Vector", "user_session_overview_session_details_button_title") + } + /// Session + public static var userSessionOverviewSessionTitle: String { + return VectorL10n.tr("Vector", "user_session_overview_session_title") + } /// Unverified session public static var userSessionUnverified: String { return VectorL10n.tr("Vector", "user_session_unverified") diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift index 9e006289b..289b3abac 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift @@ -39,14 +39,16 @@ struct UserSessionOverview: View { }) .padding(16) SwiftUI.Section { - UserSessionOverviewDisclosureCell(title: "Session details", onBackgroundTap: { + UserSessionOverviewDisclosureCell(title: VectorL10n.userSessionOverviewSessionDetailsButtonTitle, onBackgroundTap: { viewModel.send(viewAction: .viewSessionDetails) }) } } .background(theme.colors.system.ignoresSafeArea()) .frame(maxHeight: .infinity) - .navigationTitle(viewModel.viewState.isCurrentSession ? "Current session" : "Session") + .navigationTitle(viewModel.viewState.isCurrentSession ? + VectorL10n.userSessionOverviewCurrentSessionTitle : + VectorL10n.userSessionOverviewSessionTitle) } } From e2e563e44d5875e29ccc5e0bc6d369af6f1b100d Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Mon, 26 Sep 2022 13:46:08 +0300 Subject: [PATCH 050/104] Preview added --- .../View/UserSessionOverviewDisclosureCell.swift | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift index 64e66b5ee..bd6ed70b0 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,3 +43,16 @@ struct UserSessionOverviewDisclosureCell: View { } } } + +struct UserSessionOverviewDisclosureCell_Previews: PreviewProvider { + static var previews: some View { + Group { + UserSessionOverviewDisclosureCell(title: "Title") + .theme(.light) + .preferredColorScheme(.light) + UserSessionOverviewDisclosureCell(title: "Title") + .theme(.dark) + .preferredColorScheme(.dark) + } + } +} From c906e255f3c92d0971ddc0c27e47a905c3620a8f Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Mon, 26 Sep 2022 16:56:34 +0300 Subject: [PATCH 051/104] Unit and ui tests --- .../Modules/Common/Mock/MockAppScreens.swift | 1 + .../UserSessionOverviewCoordinator.swift | 2 +- .../Test/UI/UserSessionOverviewUITests.swift | 41 ++++--------- .../UserSessionOverviewViewModelTests.swift | 57 ++++++++++--------- .../UserSessionOverviewModels.swift | 17 +++++- .../UserSessionOverviewViewModel.swift | 2 +- .../UserSessionOverviewDisclosureCell.swift | 29 +++++----- 7 files changed, 71 insertions(+), 78 deletions(-) diff --git a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift index a87521164..8d5a62e63 100644 --- a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift +++ b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift @@ -21,6 +21,7 @@ enum MockAppScreens { static let appScreens: [MockScreenState.Type] = [ MockUserSessionsOverviewScreenState.self, MockUserSessionDetailsScreenState.self, + MockUserSessionOverviewScreenState.self, MockLiveLocationLabPromotionScreenState.self, MockLiveLocationSharingViewerScreenState.self, MockAuthenticationLoginScreenState.self, diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift index eedacbf9a..5b6c94ad5 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift @@ -63,7 +63,7 @@ final class UserSessionOverviewCoordinator: Coordinator, Presentable { switch result { case .verifyCurrentSession: break // TODO - case let .showCurrentSessionDetails(sessionInfo: sessionInfo): + case let .showSessionDetails(sessionInfo: sessionInfo): self.completion?(.openSessionDetails(session: sessionInfo)) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift index 5958a9a0a..066a75647 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift @@ -18,40 +18,19 @@ import XCTest import RiotSwiftUI class UserSessionOverviewUITests: MockScreenTestCase { - func testUserSessionOverviewPresenceIdle() { - let presence = UserSessionOverviewPresence.idle - app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.presence(presence).title) - - let presenceText = app.staticTexts["presenceText"] - XCTAssert(presenceText.exists) - XCTAssertEqual(presenceText.label, presence.title) + + func test_whenCurrentSessionSelected_correctNavTittleDisplayed() { + app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.currentSession.title) + XCTAssertTrue(app.navigationBars[VectorL10n.userSessionOverviewCurrentSessionTitle].staticTexts[VectorL10n.userSessionOverviewCurrentSessionTitle].exists) } - func testUserSessionOverviewPresenceOffline() { - let presence = UserSessionOverviewPresence.offline - app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.presence(presence).title) - - let presenceText = app.staticTexts["presenceText"] - XCTAssert(presenceText.exists) - XCTAssertEqual(presenceText.label, presence.title) + func test_whenOtherSessionSelected_correctNavTittleDisplayed() { + app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.otherSession.title) + XCTAssertTrue(app.navigationBars[VectorL10n.userSessionOverviewSessionTitle].staticTexts[VectorL10n.userSessionOverviewSessionTitle].exists) } - func testUserSessionOverviewPresenceOnline() { - let presence = UserSessionOverviewPresence.online - app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.presence(presence).title) - - let presenceText = app.staticTexts["presenceText"] - XCTAssert(presenceText.exists) - XCTAssertEqual(presenceText.label, presence.title) + func test_whenSessionOverviewPresented_sessionDetailsButtonExists() { + app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.currentSession.title) + XCTAssertTrue(app.buttons[VectorL10n.userSessionOverviewSessionDetailsButtonTitle].exists) } - - func testUserSessionOverviewLongName() { - let name = "Somebody with a super long name we would like to test" - app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.longDisplayName(name).title) - - let displayNameText = app.staticTexts["displayNameText"] - XCTAssert(displayNameText.exists) - XCTAssertEqual(displayNameText.label, name) - } - } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift index aac52cb0f..f3bfe84a6 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift @@ -20,37 +20,38 @@ import Combine @testable import RiotSwiftUI class UserSessionOverviewViewModelTests: XCTestCase { - private enum Constants { - static let presenceInitialValue: UserSessionOverviewPresence = .offline - static let displayName = "Alice" - } - var service: MockUserSessionOverviewService! - var viewModel: UserSessionOverviewViewModelProtocol! - var context: UserSessionOverviewViewModelType.Context! - var cancellables = Set() - override func setUpWithError() throws { - service = MockUserSessionOverviewService(displayName: Constants.displayName, presence: Constants.presenceInitialValue) - viewModel = UserSessionOverviewViewModel.makeUserSessionOverviewViewModel(userSessionOverviewService: service) - context = viewModel.context - } - func testInitialState() { - XCTAssertEqual(context.viewState.displayName, Constants.displayName) - XCTAssertEqual(context.viewState.presence, Constants.presenceInitialValue) - } + var sut: UserSessionOverviewViewModel! - func testFirstPresenceReceived() throws { - let presencePublisher = context.$viewState.map(\.presence).removeDuplicates().collect(1).first() - XCTAssertEqual(try xcAwait(presencePublisher), [Constants.presenceInitialValue]) + func test_whenVerifyCurrentSessionProcessed_completionWithVerifyCurrentSessionCalled() { + sut = UserSessionOverviewViewModel(userSessionInfo: createUserSessionInfo(), isCurrentSession: true) + + var modelResult: UserSessionOverviewViewModelResult? + sut.completion = { result in + modelResult = result + } + sut.process(viewAction: .verifyCurrentSession) + XCTAssertEqual(modelResult, .verifyCurrentSession) } + + func test_whenViewSessionDetailsProcessed_completionWithShowSessionDetailsCalled() { + let session = createUserSessionInfo() + sut = UserSessionOverviewViewModel(userSessionInfo: session, isCurrentSession: true) - func testPresenceUpdatesReceived() throws { - let presencePublisher = context.$viewState.map(\.presence).removeDuplicates().collect(3).first() - let awaitDeferred = xcAwaitDeferred(presencePublisher) - let newPresenceValue1: UserSessionOverviewPresence = .online - let newPresenceValue2: UserSessionOverviewPresence = .idle - service.simulateUpdate(presence: newPresenceValue1) - service.simulateUpdate(presence: newPresenceValue2) - XCTAssertEqual(try awaitDeferred(), [Constants.presenceInitialValue, newPresenceValue1, newPresenceValue2]) + var modelResult: UserSessionOverviewViewModelResult? + sut.completion = { result in + modelResult = result + } + sut.process(viewAction: .viewSessionDetails) + XCTAssertEqual(modelResult, .showSessionDetails(sessionInfo: session)) + } + + private func createUserSessionInfo() -> UserSessionInfo { + UserSessionInfo(sessionId: "session", + sessionName: "iOS", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift index 40284c91c..9283df5c7 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift @@ -24,11 +24,24 @@ enum UserSessionOverviewCoordinatorResult { // MARK: View model -enum UserSessionOverviewViewModelResult { - case showCurrentSessionDetails(sessionInfo: UserSessionInfo) +enum UserSessionOverviewViewModelResult: Equatable { + case showSessionDetails(sessionInfo: UserSessionInfo) case verifyCurrentSession } +extension UserSessionOverviewViewModelResult { + static func == (lhs: UserSessionOverviewViewModelResult, rhs: UserSessionOverviewViewModelResult) -> Bool { + switch (lhs, rhs) { + case (.verifyCurrentSession, .verifyCurrentSession): + return true + case (let .showSessionDetails(session), let .showSessionDetails(session2)): + return session.id == session2.id + default: + return false + } + } +} + // MARK: View struct UserSessionOverviewViewState: BindableState { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift index 3eb381e02..efb8063dc 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift @@ -47,7 +47,7 @@ class UserSessionOverviewViewModel: UserSessionOverviewViewModelType, UserSessio case .verifyCurrentSession: completion?(.verifyCurrentSession) case .viewSessionDetails: - completion?(.showCurrentSessionDetails(sessionInfo: userSessionInfo)) + completion?(.showSessionDetails(sessionInfo: userSessionInfo)) } } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift index bd6ed70b0..b63640ada 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift @@ -24,22 +24,21 @@ struct UserSessionOverviewDisclosureCell: View { var onBackgroundTap: (() -> (Void))? = nil var body: some View { - VStack(spacing: 0) { - SeparatorLine() - HStack() { - Text(title) - .font(theme.fonts.body) - .foregroundColor(theme.colors.primaryContent) - .frame(maxWidth: .infinity, alignment: .leading) - Image(Asset.Images.chevron.name) + Button(action: { onBackgroundTap?()}) { + VStack(spacing: 0) { + SeparatorLine() + HStack() { + Text(title) + .font(theme.fonts.body) + .foregroundColor(theme.colors.primaryContent) + .frame(maxWidth: .infinity, alignment: .leading) + Image(Asset.Images.chevron.name) + } + .padding(.vertical, 12) + .padding(.horizontal, 16) + SeparatorLine() } - .padding(.vertical, 12) - .padding(.horizontal, 16) - SeparatorLine() - } - .background(theme.colors.background) - .onTapGesture { - onBackgroundTap?() + .background(theme.colors.background) } } } From 1a13b1afda4ebb7a9d8a9ed49719fb725fbb48a7 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Mon, 26 Sep 2022 17:22:50 +0300 Subject: [PATCH 052/104] Changelog --- changelog.d/6753.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6753.wip diff --git a/changelog.d/6753.wip b/changelog.d/6753.wip new file mode 100644 index 000000000..a96560d05 --- /dev/null +++ b/changelog.d/6753.wip @@ -0,0 +1 @@ +Device manager: Session overview screen. From bbe15aa55776120ed17ea2c061aed84d9cfc1d34 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Mon, 26 Sep 2022 17:26:47 +0300 Subject: [PATCH 053/104] Moved SeparatorLine --- .../UserSessions/Common/SeparatorLine.swift | 29 +++++++++++++++++++ .../View/UserSessionOverview.swift | 12 +------- 2 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 RiotSwiftUI/Modules/UserSessions/Common/SeparatorLine.swift diff --git a/RiotSwiftUI/Modules/UserSessions/Common/SeparatorLine.swift b/RiotSwiftUI/Modules/UserSessions/Common/SeparatorLine.swift new file mode 100644 index 000000000..a0ae9c043 --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/Common/SeparatorLine.swift @@ -0,0 +1,29 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct SeparatorLine: View { + + @Environment(\.theme) private var theme: ThemeSwiftUI + + var body: some View { + Rectangle() + .fill(theme.colors.quinaryContent) + .frame(maxWidth: .infinity, alignment: .trailing) + .frame(height: 1.0) + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift index 289b3abac..9a193c27e 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift @@ -52,17 +52,7 @@ struct UserSessionOverview: View { } } -struct SeparatorLine: View { - - @Environment(\.theme) private var theme: ThemeSwiftUI - - var body: some View { - Rectangle() - .fill(theme.colors.quinaryContent) - .frame(maxWidth: .infinity, alignment: .trailing) - .frame(height: 1.0) - } -} + // MARK: - Previews From cb261521660dd11abdcdc107ec40b80cd4fc9f6b Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Mon, 26 Sep 2022 16:35:48 +0200 Subject: [PATCH 054/104] Add AppInfo to test target --- RiotTests/target.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/RiotTests/target.yml b/RiotTests/target.yml index 990253346..66478be09 100644 --- a/RiotTests/target.yml +++ b/RiotTests/target.yml @@ -60,6 +60,7 @@ targets: - path: ../Config/AppConfiguration.swift - path: ../Config/CommonConfiguration.swift - path: ../Riot/Categories/Bundle.swift + - path: ../Riot/Managers/AppInfo/AppInfo.swift - path: ../Riot/Managers/Settings/RiotSettings.swift - path: ../Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift - path: ../Riot/Managers/KeyValueStorage/ From 7bd388887b7c2e5c5e48f51e86e7a80ba914ae45 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Mon, 26 Sep 2022 17:37:22 +0300 Subject: [PATCH 055/104] Cleanup --- .../Coordinator/UserSessionOverviewCoordinator.swift | 3 ++- .../Test/UI/UserSessionOverviewUITests.swift | 10 ++++++---- .../UserSessionOverview/View/UserSessionOverview.swift | 2 -- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift index 5b6c94ad5..0748a7347 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift @@ -45,7 +45,8 @@ final class UserSessionOverviewCoordinator: Coordinator, Presentable { init(parameters: UserSessionOverviewCoordinatorParameters) { self.parameters = parameters - let viewModel = UserSessionOverviewViewModel(userSessionInfo: parameters.userSessionInfo, isCurrentSession: parameters.isCurrentSession) + let viewModel = UserSessionOverviewViewModel(userSessionInfo: parameters.userSessionInfo, + isCurrentSession: parameters.isCurrentSession) let view = UserSessionOverview(viewModel: viewModel.context) userSessionOverviewViewModel = viewModel userSessionOverviewHostingController = VectorHostingController(rootView: view) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift index 066a75647..192f0a9db 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift @@ -20,17 +20,19 @@ import RiotSwiftUI class UserSessionOverviewUITests: MockScreenTestCase { func test_whenCurrentSessionSelected_correctNavTittleDisplayed() { - app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.currentSession.title) - XCTAssertTrue(app.navigationBars[VectorL10n.userSessionOverviewCurrentSessionTitle].staticTexts[VectorL10n.userSessionOverviewCurrentSessionTitle].exists) + app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.currentSession.title) + let navTitle = VectorL10n.userSessionOverviewCurrentSessionTitle + XCTAssertTrue(app.navigationBars[navTitle].staticTexts[navTitle].exists) } func test_whenOtherSessionSelected_correctNavTittleDisplayed() { app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.otherSession.title) - XCTAssertTrue(app.navigationBars[VectorL10n.userSessionOverviewSessionTitle].staticTexts[VectorL10n.userSessionOverviewSessionTitle].exists) + let navTitle = VectorL10n.userSessionOverviewSessionTitle + XCTAssertTrue(app.navigationBars[navTitle].staticTexts[navTitle].exists) } func test_whenSessionOverviewPresented_sessionDetailsButtonExists() { - app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.currentSession.title) + app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.currentSession.title) XCTAssertTrue(app.buttons[VectorL10n.userSessionOverviewSessionDetailsButtonTitle].exists) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift index 9a193c27e..0590fbfef 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift @@ -52,8 +52,6 @@ struct UserSessionOverview: View { } } - - // MARK: - Previews struct UserSessionOverview_Previews: PreviewProvider { From f7e3bbc454b1c40678afe20475b77b0397e5bc53 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Mon, 26 Sep 2022 17:33:11 +0200 Subject: [PATCH 056/104] Add AppVersion to test target --- RiotTests/target.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/RiotTests/target.yml b/RiotTests/target.yml index 66478be09..cde05e9ef 100644 --- a/RiotTests/target.yml +++ b/RiotTests/target.yml @@ -61,6 +61,7 @@ targets: - path: ../Config/CommonConfiguration.swift - path: ../Riot/Categories/Bundle.swift - path: ../Riot/Managers/AppInfo/AppInfo.swift + - path: ../Riot/Managers/AppInfo/AppVersion.swift - path: ../Riot/Managers/Settings/RiotSettings.swift - path: ../Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift - path: ../Riot/Managers/KeyValueStorage/ From f83fce206485346d803847914fe909b299de0a84 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 26 Sep 2022 18:21:34 +0300 Subject: [PATCH 057/104] Cleaned up various things, mostly removed unnecessary `self`s, comments and bad formatting --- .../UserSessions/Common/SeparatorLine.swift | 1 - .../DeviceAvatar/DeviceAvatarView.swift | 3 - .../DeviceAvatar/DeviceAvatarViewData.swift | 2 - .../DeviceType/DeviceType+Element.swift | 58 ---------------- .../UserSessions/DeviceType/DeviceType.swift | 28 ++++++++ .../UserSessionCardView.swift | 11 ++- .../UserSessionCardViewData.swift | 22 +++--- .../UserSessionDetailsCoordinator.swift | 12 ++-- .../Test/UI/UserSessionDetailsUITests.swift | 1 - .../UserSessionDetailsViewModelTests.swift | 1 - .../UserSessionDetailsModels.swift | 3 - .../UserSessionDetailsViewModel.swift | 9 --- .../UserSessionDetailsViewModelProtocol.swift | 1 - .../View/UserSessionDetails.swift | 7 -- .../View/UserSessionDetailsItem.swift | 7 -- .../UserSessionLastActivityFormatter.swift | 5 -- .../UserSessionNameFormatter.swift | 4 +- .../UserSessionInfo/UserSessionInfo.swift | 8 +-- .../UserSessionOverviewCoordinator.swift | 25 +++---- .../MockUserSessionOverviewScreenState.swift | 6 +- .../Test/UI/UserSessionOverviewUITests.swift | 1 - .../UserSessionOverviewViewModelTests.swift | 1 - .../UserSessionOverviewViewModel.swift | 7 -- ...UserSessionOverviewViewModelProtocol.swift | 1 - .../View/UserSessionOverview.swift | 7 -- .../UserSessionOverviewDisclosureCell.swift | 1 - .../UserSessionsFlowCoordinator.swift | 27 +++----- ...ssionsFlowCoordinatorBridgePresenter.swift | 29 ++------ .../UserSessionsOverviewCoordinator.swift | 30 +++----- .../UserSessionsOverviewService.swift | 39 ++++------- .../MockUserSessionsOverviewService.swift | 8 ++- .../UserSessionsOverviewServiceProtocol.swift | 2 - .../UserSessionsOverviewViewModelTests.swift | 1 - .../UserSessionsOverviewModels.swift | 11 ++- .../UserSessionsOverviewViewModel.swift | 68 +++++++------------ ...serSessionsOverviewViewModelProtocol.swift | 1 - .../View/UserSessionListItem.swift | 17 +---- .../View/UserSessionListItemViewData.swift | 17 ++--- .../View/UserSessionsOverview.swift | 9 +-- 39 files changed, 141 insertions(+), 350 deletions(-) delete mode 100644 RiotSwiftUI/Modules/UserSessions/DeviceType/DeviceType+Element.swift diff --git a/RiotSwiftUI/Modules/UserSessions/Common/SeparatorLine.swift b/RiotSwiftUI/Modules/UserSessions/Common/SeparatorLine.swift index a0ae9c043..87d1a47d8 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/SeparatorLine.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/SeparatorLine.swift @@ -17,7 +17,6 @@ import SwiftUI struct SeparatorLine: View { - @Environment(\.theme) private var theme: ThemeSwiftUI var body: some View { diff --git a/RiotSwiftUI/Modules/UserSessions/DeviceAvatar/DeviceAvatarView.swift b/RiotSwiftUI/Modules/UserSessions/DeviceAvatar/DeviceAvatarView.swift index a24567171..24a142619 100644 --- a/RiotSwiftUI/Modules/UserSessions/DeviceAvatar/DeviceAvatarView.swift +++ b/RiotSwiftUI/Modules/UserSessions/DeviceAvatar/DeviceAvatarView.swift @@ -19,7 +19,6 @@ import DesignKit /// Avatar view for device struct DeviceAvatarView: View { - @Environment(\.theme) var theme: ThemeSwiftUI var viewData: DeviceAvatarViewData @@ -55,7 +54,6 @@ struct DeviceAvatarView: View { } struct DeviceAvatarViewListPreview: View { - var viewDataList: [DeviceAvatarViewData] { return [ DeviceAvatarViewData(deviceType: .desktop, isVerified: true), @@ -78,7 +76,6 @@ struct DeviceAvatarViewListPreview: View { } struct DeviceAvatarView_Previews: PreviewProvider { - static var previews: some View { Group { DeviceAvatarViewListPreview().theme(.light).preferredColorScheme(.light) diff --git a/RiotSwiftUI/Modules/UserSessions/DeviceAvatar/DeviceAvatarViewData.swift b/RiotSwiftUI/Modules/UserSessions/DeviceAvatar/DeviceAvatarViewData.swift index 19c6cfd1d..e4dd59b26 100644 --- a/RiotSwiftUI/Modules/UserSessions/DeviceAvatar/DeviceAvatarViewData.swift +++ b/RiotSwiftUI/Modules/UserSessions/DeviceAvatar/DeviceAvatarViewData.swift @@ -19,8 +19,6 @@ import SwiftUI /// View data for DeviceAvatarView struct DeviceAvatarViewData { - let deviceType: DeviceType - let isVerified: Bool? } diff --git a/RiotSwiftUI/Modules/UserSessions/DeviceType/DeviceType+Element.swift b/RiotSwiftUI/Modules/UserSessions/DeviceType/DeviceType+Element.swift deleted file mode 100644 index ecbad3537..000000000 --- a/RiotSwiftUI/Modules/UserSessions/DeviceType/DeviceType+Element.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright 2022 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import SwiftUI - -extension DeviceType { - - var image: Image { - - let image: Image - - switch self { - case .desktop: - image = Image(Asset.Images.deviceTypeDesktop.name) - case .web: - image = Image(Asset.Images.deviceTypeWeb.name) - case .mobile: - image = Image(Asset.Images.deviceTypeMobile.name) - case .unknown: - image = Image(Asset.Images.deviceTypeUnknown.name) - } - - return image - } - - var name: String { - let name: String - - let appName = AppInfo.current.displayName - - switch self { - case .desktop: - name = VectorL10n.deviceNameDesktop(appName) - case .web: - name = VectorL10n.deviceNameWeb(appName) - case .mobile: - name = VectorL10n.deviceNameMobile(appName) - case .unknown: - name = VectorL10n.deviceNameUnknown - } - - return name - } -} diff --git a/RiotSwiftUI/Modules/UserSessions/DeviceType/DeviceType.swift b/RiotSwiftUI/Modules/UserSessions/DeviceType/DeviceType.swift index 1ae053b1a..7410be89a 100644 --- a/RiotSwiftUI/Modules/UserSessions/DeviceType/DeviceType.swift +++ b/RiotSwiftUI/Modules/UserSessions/DeviceType/DeviceType.swift @@ -23,4 +23,32 @@ enum DeviceType { case web case mobile case unknown + + var image: Image { + switch self { + case .desktop: + return Image(Asset.Images.deviceTypeDesktop.name) + case .web: + return Image(Asset.Images.deviceTypeWeb.name) + case .mobile: + return Image(Asset.Images.deviceTypeMobile.name) + case .unknown: + return Image(Asset.Images.deviceTypeUnknown.name) + } + } + + var name: String { + let appName = AppInfo.current.displayName + + switch self { + case .desktop: + return VectorL10n.deviceNameDesktop(appName) + case .web: + return VectorL10n.deviceNameWeb(appName) + case .mobile: + return VectorL10n.deviceNameMobile(appName) + case .unknown: + return VectorL10n.deviceNameUnknown + } + } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardView.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardView.swift index 7032d5701..b423f4e7c 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardView.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardView.swift @@ -18,7 +18,6 @@ import SwiftUI import DesignKit struct UserSessionCardView: View { - @Environment(\.theme) var theme: ThemeSwiftUI var viewData: UserSessionCardViewData @@ -82,7 +81,7 @@ struct UserSessionCardView: View { .multilineTextAlignment(.center) } - if self.showExtraInformations { + if showExtraInformations { VStack(spacing: 2) { if let lastActivityDateString = viewData.lastActivityDateString, lastActivityDateString.isEmpty == false { Text(lastActivityDateString) @@ -126,13 +125,12 @@ struct UserSessionCardView: View { .padding(24) .frame(maxWidth: .infinity) .background(theme.colors.background) - .clipShape(self.backgroundShape) - .shapedBorder(color: theme.colors.quinaryContent, borderWidth: 1.0, shape: self.backgroundShape) + .clipShape(backgroundShape) + .shapedBorder(color: theme.colors.quinaryContent, borderWidth: 1.0, shape: backgroundShape) } } struct UserSessionCardViewPreview: View { - @Environment(\.theme) var theme: ThemeSwiftUI let viewData: UserSessionCardViewData @@ -145,7 +143,7 @@ struct UserSessionCardViewPreview: View { var body: some View { VStack { - UserSessionCardView(viewData: self.viewData) + UserSessionCardView(viewData: viewData) } .frame(maxWidth: .infinity) .background(theme.colors.system) @@ -154,7 +152,6 @@ struct UserSessionCardViewPreview: View { } struct UserSessionCardView_Previews: PreviewProvider { - static var previews: some View { Group { UserSessionCardViewPreview(isCurrentSessionInfo: true).theme(.light).preferredColorScheme(.light) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardViewData.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardViewData.swift index d63175a0e..f36cf0573 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardViewData.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardViewData.swift @@ -18,13 +18,8 @@ import Foundation /// View data for UserSessionCardView struct UserSessionCardViewData { - - // MARK: - Constants - private static let userSessionNameFormatter = UserSessionNameFormatter() private static let lastActivityDateFormatter = UserSessionLastActivityFormatter() - - // MARK: - Properties var id: String { return sessionId @@ -45,8 +40,6 @@ struct UserSessionCardViewData { /// Indicate if the current user session is shown and to adpat the layout let isCurrentSessionDisplayMode: Bool - // MARK: - Setup - init(sessionId: String, sessionDisplayName: String?, deviceType: DeviceType, @@ -55,7 +48,7 @@ struct UserSessionCardViewData { lastSeenIP: String?, isCurrentSessionDisplayMode: Bool = false) { self.sessionId = sessionId - self.sessionName = Self.userSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName) + sessionName = Self.userSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName) self.isVerified = isVerified var lastActivityDateString: String? @@ -65,16 +58,21 @@ struct UserSessionCardViewData { } self.lastActivityDateString = lastActivityDateString - self.lastSeenIPInfo = lastSeenIP - self.deviceAvatarViewData = DeviceAvatarViewData(deviceType: deviceType, isVerified: nil) + lastSeenIPInfo = lastSeenIP + deviceAvatarViewData = DeviceAvatarViewData(deviceType: deviceType, isVerified: nil) self.isCurrentSessionDisplayMode = isCurrentSessionDisplayMode } } extension UserSessionCardViewData { - init(userSessionInfo: UserSessionInfo, isCurrentSessionDisplayMode: Bool = false) { - self.init(sessionId: userSessionInfo.sessionId, sessionDisplayName: userSessionInfo.sessionName, deviceType: userSessionInfo.deviceType, isVerified: userSessionInfo.isVerified, lastActivityTimestamp: userSessionInfo.lastSeenTimestamp, lastSeenIP: userSessionInfo.lastSeenIP, isCurrentSessionDisplayMode: isCurrentSessionDisplayMode) + self.init(sessionId: userSessionInfo.sessionId, + sessionDisplayName: userSessionInfo.sessionName, + deviceType: userSessionInfo.deviceType, + isVerified: userSessionInfo.isVerified, + lastActivityTimestamp: userSessionInfo.lastSeenTimestamp, + lastSeenIP: userSessionInfo.lastSeenIP, + isCurrentSessionDisplayMode: isCurrentSessionDisplayMode) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift index 28b16168a..c013711ae 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift @@ -22,11 +22,6 @@ struct UserSessionDetailsCoordinatorParameters { } final class UserSessionDetailsCoordinator: Coordinator, Presentable { - - // MARK: - Properties - - // MARK: Private - private let parameters: UserSessionDetailsCoordinatorParameters private let userSessionDetailsHostingController: UIViewController private var userSessionDetailsViewModel: UserSessionDetailsViewModelProtocol @@ -58,13 +53,16 @@ final class UserSessionDetailsCoordinator: Coordinator, Presentable { func start() { MXLog.debug("[UserSessionDetailsCoordinator] did start.") userSessionDetailsViewModel.completion = { [weak self] result in - guard let self = self else { return } + guard let self = self else { + return + } + MXLog.debug("[UserSessionDetailsCoordinator] UserSessionDetailsViewModel did complete with result: \(result).") self.completion?(result) } } func toPresentable() -> UIViewController { - return self.userSessionDetailsHostingController + return userSessionDetailsHostingController } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/UI/UserSessionDetailsUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/UI/UserSessionDetailsUITests.swift index 60c5308f4..eb9f33e79 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/UI/UserSessionDetailsUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/UI/UserSessionDetailsUITests.swift @@ -18,7 +18,6 @@ import XCTest import RiotSwiftUI class UserSessionDetailsUITests: MockScreenTestCase { - func test_longPressDetailsCell_CopiesValueToClipboard() throws { app.goToScreenWithIdentifier(MockUserSessionDetailsScreenState.allSections.title) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift index c5aa629b5..70026cac0 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift @@ -19,7 +19,6 @@ import XCTest @testable import RiotSwiftUI class UserSessionDetailsViewModelTests: XCTestCase { - func test_whenSessionNameAndLastSeenIPNil_viewStateCorrect() { let userSessionInfo = createUserSessionInfo(sessionId: "session", sessionName: nil, diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsModels.swift index 38d4cf31a..0d994add3 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsModels.swift @@ -29,7 +29,6 @@ enum UserSessionDetailsViewAction { } struct UserSessionDetailsViewState: BindableState, Equatable { - let sections: [UserSessionDetailsSectionViewData] } @@ -47,7 +46,6 @@ struct UserSessionDetailsSectionItemViewData: Identifiable { } extension UserSessionDetailsSectionViewData: Equatable { - static func == (lhs: UserSessionDetailsSectionViewData, rhs: UserSessionDetailsSectionViewData) -> Bool { lhs.header == rhs.header && lhs.footer == rhs.footer && @@ -56,7 +54,6 @@ extension UserSessionDetailsSectionViewData: Equatable { } extension UserSessionDetailsSectionItemViewData: Equatable { - static func == (lhs: UserSessionDetailsSectionItemViewData, rhs: UserSessionDetailsSectionItemViewData) -> Bool { lhs.title == rhs.title && lhs.value == rhs.value diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift index c4b04bfec..3625f7026 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift @@ -21,17 +21,8 @@ typealias UserSessionDetailsViewModelType = StateStoreViewModel class UserSessionDetailsViewModel: UserSessionDetailsViewModelType, UserSessionDetailsViewModelProtocol { - - // MARK: - Properties - - // MARK: Private - - // MARK: Public - var completion: ((UserSessionDetailsViewModelResult) -> Void)? - // MARK: - Setup - init(userSessionInfo: UserSessionInfo) { super.init(initialViewState: UserSessionDetailsViewState(sections: [])) updateViewState(userSessionInfo: userSessionInfo) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModelProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModelProtocol.swift index 8222cc770..0def51625 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModelProtocol.swift @@ -17,7 +17,6 @@ import Foundation protocol UserSessionDetailsViewModelProtocol { - var completion: ((UserSessionDetailsViewModelResult) -> Void)? { get set } var context: UserSessionDetailsViewModelType.Context { get } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetails.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetails.swift index 3be803271..fb89d2eaf 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetails.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetails.swift @@ -17,11 +17,6 @@ import SwiftUI struct UserSessionDetails: View { - - // MARK: - Properties - - // MARK: Private - private enum LayoutConstants { static let listItemHorizontalPadding: CGFloat = 20 static let sectionVerticalPadding: CGFloat = 8 @@ -29,8 +24,6 @@ struct UserSessionDetails: View { @Environment(\.theme) private var theme: ThemeSwiftUI - // MARK: Public - @ObservedObject var viewModel: UserSessionDetailsViewModel.Context var body: some View { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetailsItem.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetailsItem.swift index 870ac606a..c484ae35c 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetailsItem.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetailsItem.swift @@ -17,15 +17,8 @@ import SwiftUI struct UserSessionDetailsItem: View { - - // MARK: - Properties - - // MARK: Private - @Environment(\.theme) private var theme: ThemeSwiftUI - // MARK: Public - let viewData: UserSessionDetailsSectionItemViewData let horizontalPadding: CGFloat diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionFormatters/UserSessionLastActivityFormatter.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionFormatters/UserSessionLastActivityFormatter.swift index 891e0919b..a3482d466 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionFormatters/UserSessionLastActivityFormatter.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionFormatters/UserSessionLastActivityFormatter.swift @@ -18,9 +18,6 @@ import Foundation /// Enables to build last activity date string class UserSessionLastActivityFormatter { - - // MARK: - Constants - private static var lastActivityDateFormatter: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.locale = Locale.current @@ -30,8 +27,6 @@ class UserSessionLastActivityFormatter { return dateFormatter }() - // MARK: - Public - /// Session last activity string func lastActivityDateString(from lastActivityTimestamp: TimeInterval) -> String { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionFormatters/UserSessionNameFormatter.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionFormatters/UserSessionNameFormatter.swift index 0aed1082f..375aa8034 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionFormatters/UserSessionNameFormatter.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionFormatters/UserSessionNameFormatter.swift @@ -17,11 +17,9 @@ import Foundation /// Enables to build user session name -class UserSessionNameFormatter { - +class UserSessionNameFormatter { /// Session name with client name and session display name func sessionName(deviceType: DeviceType, sessionDisplayName: String?) -> String { - let sessionName: String let clientName = deviceType.name diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionInfo/UserSessionInfo.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionInfo/UserSessionInfo.swift index 6009bfcb2..f8679b6df 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionInfo/UserSessionInfo.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionInfo/UserSessionInfo.swift @@ -18,7 +18,6 @@ import Foundation /// Represents a user session information struct UserSessionInfo: Identifiable { - /// Delay after which session is considered inactive, 90 days static let inactiveSessionDurationTreshold: TimeInterval = 90 * 86400 @@ -67,12 +66,9 @@ struct UserSessionInfo: Identifiable { if let lastSeenTimestamp = lastSeenTimestamp { let elapsedTime = Date().timeIntervalSince1970 - lastSeenTimestamp - - let isSessionInactive = elapsedTime >= Self.inactiveSessionDurationTreshold - - self.isSessionActive = !isSessionInactive + isSessionActive = elapsedTime < Self.inactiveSessionDurationTreshold } else { - self.isSessionActive = true + isSessionActive = true } } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift index 0748a7347..c7560d5f4 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift @@ -23,14 +23,9 @@ struct UserSessionOverviewCoordinatorParameters { } final class UserSessionOverviewCoordinator: Coordinator, Presentable { - - // MARK: - Properties - - // MARK: Private - private let parameters: UserSessionOverviewCoordinatorParameters - private let userSessionOverviewHostingController: UIViewController - private var userSessionOverviewViewModel: UserSessionOverviewViewModelProtocol + private let hostingController: UIViewController + private var viewModel: UserSessionOverviewViewModelProtocol private var indicatorPresenter: UserIndicatorTypePresenterProtocol private var loadingIndicator: UserIndicator? @@ -45,20 +40,20 @@ final class UserSessionOverviewCoordinator: Coordinator, Presentable { init(parameters: UserSessionOverviewCoordinatorParameters) { self.parameters = parameters - let viewModel = UserSessionOverviewViewModel(userSessionInfo: parameters.userSessionInfo, - isCurrentSession: parameters.isCurrentSession) - let view = UserSessionOverview(viewModel: viewModel.context) - userSessionOverviewViewModel = viewModel - userSessionOverviewHostingController = VectorHostingController(rootView: view) + + viewModel = UserSessionOverviewViewModel(userSessionInfo: parameters.userSessionInfo, + isCurrentSession: parameters.isCurrentSession) - indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: userSessionOverviewHostingController) + hostingController = VectorHostingController(rootView: UserSessionOverview(viewModel: viewModel.context)) + + indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: hostingController) } // MARK: - Public func start() { MXLog.debug("[UserSessionOverviewCoordinator] did start.") - userSessionOverviewViewModel.completion = { [weak self] result in + viewModel.completion = { [weak self] result in guard let self = self else { return } MXLog.debug("[UserSessionOverviewCoordinator] UserSessionOverviewViewModel did complete with result: \(result).") switch result { @@ -71,7 +66,7 @@ final class UserSessionOverviewCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.userSessionOverviewHostingController + return hostingController } // MARK: - Private diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift index a24ea52bb..85f22dbd0 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift @@ -60,10 +60,6 @@ enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { } // can simulate service and viewModel actions here if needs be. - - return ( - [viewModel], - AnyView(UserSessionOverview(viewModel: viewModel.context)) - ) + return ([viewModel], AnyView(UserSessionOverview(viewModel: viewModel.context))) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift index 192f0a9db..fa9daf73e 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift @@ -18,7 +18,6 @@ import XCTest import RiotSwiftUI class UserSessionOverviewUITests: MockScreenTestCase { - func test_whenCurrentSessionSelected_correctNavTittleDisplayed() { app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.currentSession.title) let navTitle = VectorL10n.userSessionOverviewCurrentSessionTitle diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift index f3bfe84a6..322b56766 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift @@ -20,7 +20,6 @@ import Combine @testable import RiotSwiftUI class UserSessionOverviewViewModelTests: XCTestCase { - var sut: UserSessionOverviewViewModel! func test_whenVerifyCurrentSessionProcessed_completionWithVerifyCurrentSessionCalled() { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift index efb8063dc..72cd6dc05 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift @@ -21,17 +21,10 @@ typealias UserSessionOverviewViewModelType = StateStoreViewModel class UserSessionOverviewViewModel: UserSessionOverviewViewModelType, UserSessionOverviewViewModelProtocol { - - // MARK: - Properties - - // MARK: Private private let userSessionInfo: UserSessionInfo - // MARK: Public var completion: ((UserSessionOverviewViewModelResult) -> Void)? - // MARK: - Setup - init(userSessionInfo: UserSessionInfo, isCurrentSession: Bool) { self.userSessionInfo = userSessionInfo diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModelProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModelProtocol.swift index 014dcd8a7..676695c1d 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModelProtocol.swift @@ -17,7 +17,6 @@ import Foundation protocol UserSessionOverviewViewModelProtocol { - var completion: ((UserSessionOverviewViewModelResult) -> Void)? { get set } var context: UserSessionOverviewViewModelType.Context { get } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift index 0590fbfef..98a9d4695 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift @@ -17,15 +17,8 @@ import SwiftUI struct UserSessionOverview: View { - - // MARK: - Properties - - // MARK: Private - @Environment(\.theme) private var theme: ThemeSwiftUI - // MARK: Public - @ObservedObject var viewModel: UserSessionOverviewViewModel.Context var body: some View { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift index b63640ada..027f5f0bd 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift @@ -17,7 +17,6 @@ import SwiftUI struct UserSessionOverviewDisclosureCell: View { - @Environment(\.theme) private var theme: ThemeSwiftUI let title: String diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift index db751a80f..a032dcfbd 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift @@ -22,33 +22,24 @@ struct UserSessionsFlowCoordinatorParameters { } final class UserSessionsFlowCoordinator: Coordinator, Presentable { - - // MARK: - Properties - - // MARK: Private - private let parameters: UserSessionsFlowCoordinatorParameters private let navigationRouter: NavigationRouterType - // MARK: Public - // Must be used only internally var childCoordinators: [Coordinator] = [] var completion: (() -> Void)? - // MARK: - Setup - init(parameters: UserSessionsFlowCoordinatorParameters) { self.parameters = parameters - - self.navigationRouter = parameters.router ?? NavigationRouter(navigationController: RiotNavigationController()) + navigationRouter = parameters.router ?? NavigationRouter(navigationController: RiotNavigationController()) } // MARK: - Private private func pushScreen(with coordinator: Coordinator & Presentable) { add(childCoordinator: coordinator) - self.navigationRouter.push(coordinator, animated: true, popCompletion: { [weak self] in + + navigationRouter.push(coordinator, animated: true, popCompletion: { [weak self] in self?.remove(childCoordinator: coordinator) }) @@ -56,7 +47,7 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { } private func createUserSessionsOverviewCoordinator() -> UserSessionsOverviewCoordinator { - let parameters = UserSessionsOverviewCoordinatorParameters(session: self.parameters.session) + let parameters = UserSessionsOverviewCoordinatorParameters(session: parameters.session) let coordinator = UserSessionsOverviewCoordinator(parameters: parameters) coordinator.completion = { [weak self] result in @@ -104,15 +95,15 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { let rootCoordinator = createUserSessionsOverviewCoordinator() rootCoordinator.start() - self.add(childCoordinator: rootCoordinator) + add(childCoordinator: rootCoordinator) - if self.navigationRouter.modules.isEmpty == false { - self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in + if navigationRouter.modules.isEmpty == false { + navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in self?.remove(childCoordinator: rootCoordinator) self?.completion?() }) } else { - self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in + navigationRouter.setRootModule(rootCoordinator) { [weak self] in self?.remove(childCoordinator: rootCoordinator) self?.completion?() } @@ -120,6 +111,6 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.navigationRouter.toPresentable() + return navigationRouter.toPresentable() } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinatorBridgePresenter.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinatorBridgePresenter.swift index d2a1a9ff0..a2b256177 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinatorBridgePresenter.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinatorBridgePresenter.swift @@ -24,22 +24,11 @@ import UIKit /// Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator. @objcMembers final class UserSessionsFlowCoordinatorBridgePresenter: NSObject { - - // MARK: - Constants - - // MARK: - Properties - - // MARK: Private - private let mxSession: MXSession private var coordinator: UserSessionsFlowCoordinator? - - // MARK: Public - + var completion: (() -> Void)? - // MARK: - Setup - init(mxSession: MXSession) { self.mxSession = mxSession super.init() @@ -48,26 +37,22 @@ final class UserSessionsFlowCoordinatorBridgePresenter: NSObject { // MARK: - Public func push(from navigationController: UINavigationController, animated: Bool) { - - self.startUserSessionsFlow(mxSession: self.mxSession, navigationController: navigationController) + startUserSessionsFlow(mxSession: mxSession, navigationController: navigationController) } // MARK: - Private private func startUserSessionsFlow(mxSession: MXSession, navigationController: UINavigationController?) { - var navigationRouter: NavigationRouterType? if let navigationController = navigationController { navigationRouter = NavigationRouterStore.shared.navigationRouter(for: navigationController) } - let coordinatorParameters = UserSessionsFlowCoordinatorParameters(session: mxSession, router: navigationRouter) + let parameters = UserSessionsFlowCoordinatorParameters(session: mxSession, router: navigationRouter) + let coordinator = UserSessionsFlowCoordinator(parameters: parameters) - let userSessionsFlowCoordinator = UserSessionsFlowCoordinator(parameters: coordinatorParameters) - - userSessionsFlowCoordinator.completion = { [weak self] in - + coordinator.completion = { [weak self] in guard let self = self else { return } @@ -76,8 +61,8 @@ final class UserSessionsFlowCoordinatorBridgePresenter: NSObject { self.coordinator = nil } - userSessionsFlowCoordinator.start() + coordinator.start() - self.coordinator = userSessionsFlowCoordinator + self.coordinator = coordinator } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift index b98f18fae..74afa6c23 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift @@ -22,39 +22,24 @@ struct UserSessionsOverviewCoordinatorParameters { } final class UserSessionsOverviewCoordinator: Coordinator, Presentable { - - // MARK: - Properties - - // MARK: Private - private let parameters: UserSessionsOverviewCoordinatorParameters - private let userSessionsOverviewHostingController: UIViewController - private var userSessionsOverviewViewModel: UserSessionsOverviewViewModelProtocol + private let hostingViewController: UIViewController + private var viewModel: UserSessionsOverviewViewModelProtocol private let service: UserSessionsOverviewService private var indicatorPresenter: UserIndicatorTypePresenterProtocol private var loadingIndicator: UserIndicator? - // MARK: Public - // Must be used only internally var childCoordinators: [Coordinator] = [] var completion: ((UserSessionsOverviewCoordinatorResult) -> Void)? - // MARK: - Setup - init(parameters: UserSessionsOverviewCoordinatorParameters) { self.parameters = parameters - let service = UserSessionsOverviewService(mxSession: parameters.session) - self.service = service - let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service) - let view = UserSessionsOverview(viewModel: viewModel.context) - userSessionsOverviewViewModel = viewModel - - let hostingViewController = VectorHostingController(rootView: view) - - userSessionsOverviewHostingController = hostingViewController + service = UserSessionsOverviewService(mxSession: parameters.session) + viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service) + hostingViewController = VectorHostingController(rootView: UserSessionsOverview(viewModel: viewModel.context)) indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: hostingViewController) } @@ -62,9 +47,10 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { func start() { MXLog.debug("[UserSessionsOverviewCoordinator] did start.") - userSessionsOverviewViewModel.completion = { [weak self] result in + viewModel.completion = { [weak self] result in guard let self = self else { return } MXLog.debug("[UserSessionsOverviewCoordinator] UserSessionsOverviewViewModel did complete with result: \(result).") + switch result { case .showAllUnverifiedSessions: self.showAllUnverifiedSessions() @@ -83,7 +69,7 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.userSessionsOverviewHostingController + return hostingViewController } // MARK: - Private diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift index b9efa16e0..ab242d906 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift @@ -18,33 +18,25 @@ import Foundation import MatrixSDK class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { - - // MARK: - Constants - - // MARK: - Properties - - // MARK: Private - private let mxSession: MXSession - // MARK: Public - private(set) var lastOverviewData: UserSessionsOverviewData - // MARK: - Setup - init(mxSession: MXSession) { self.mxSession = mxSession - self.lastOverviewData = UserSessionsOverviewData(currentSessionInfo: nil, unverifiedSessionsInfo: [], inactiveSessionsInfo: [], otherSessionsInfo: []) + lastOverviewData = UserSessionsOverviewData(currentSessionInfo: nil, + unverifiedSessionsInfo: [], + inactiveSessionsInfo: [], + otherSessionsInfo: []) - self.setupInitialOverviewData() + setupInitialOverviewData() } // MARK: - Public func fetchUserSessionsOverviewData(completion: @escaping (Result) -> Void) { - self.mxSession.matrixRestClient.devices { response in + mxSession.matrixRestClient.devices { response in switch response { case .success(let devices): self.lastOverviewData = self.userSessionsOverviewData(from: devices) @@ -62,21 +54,20 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { // MARK: - Private private func setupInitialOverviewData() { - let currentSessionInfo = self.getCurrentUserSessionInfoFromCache() + let currentSessionInfo = getCurrentUserSessionInfoFromCache() - self.lastOverviewData = UserSessionsOverviewData(currentSessionInfo: currentSessionInfo, unverifiedSessionsInfo: [], inactiveSessionsInfo: [], otherSessionsInfo: []) + lastOverviewData = UserSessionsOverviewData(currentSessionInfo: currentSessionInfo, unverifiedSessionsInfo: [], inactiveSessionsInfo: [], otherSessionsInfo: []) } private func getCurrentUserSessionInfoFromCache() -> UserSessionInfo? { guard let mainAccount = MXKAccountManager.shared().activeAccounts.first, let device = mainAccount.device else { return nil } - return self.userSessionInfo(from: device) + return userSessionInfo(from: device) } private func userSessionInfo(from device: MXDevice) -> UserSessionInfo { - - let deviceInfo = self.getDeviceInfo(for: device.deviceId) + let deviceInfo = getDeviceInfo(for: device.deviceId) let isSessionVerified = deviceInfo?.trustLevel.isVerified ?? false @@ -95,20 +86,20 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { } private func getDeviceInfo(for deviceId: String) -> MXDeviceInfo? { - guard let userId = self.mxSession.myUserId else { + guard let userId = mxSession.myUserId else { return nil } - return self.mxSession.crypto.device(withDeviceId: deviceId, ofUser: userId) + + return mxSession.crypto.device(withDeviceId: deviceId, ofUser: userId) } private func userSessionsOverviewData(from devices: [MXDevice]) -> UserSessionsOverviewData { - let sortedDevices = devices.sorted { device1, device2 in device1.lastSeenTs > device2.lastSeenTs } let allUserSessionInfo = sortedDevices.map { device in - return self.userSessionInfo(from: device) + return userSessionInfo(from: device) } var currentSessionInfo: UserSessionInfo? @@ -118,7 +109,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { var otherSessionsInfo: [UserSessionInfo] = [] for userSessionInfo in allUserSessionInfo { - if userSessionInfo.sessionId == self.mxSession.myDeviceId { + if userSessionInfo.sessionId == mxSession.myDeviceId { currentSessionInfo = userSessionInfo } else { otherSessionsInfo.append(userSessionInfo) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift index d0621cd9c..34d79fe16 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift @@ -17,11 +17,10 @@ import Foundation class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { - var lastOverviewData: UserSessionsOverviewData func fetchUserSessionsOverviewData(completion: @escaping (Result) -> Void) { - completion(.success(self.lastOverviewData)) + completion(.success(lastOverviewData)) } func getOtherSession(sessionId: String) -> UserSessionInfo? { @@ -41,6 +40,9 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { UserSessionInfo(sessionId: "3", sessionName: "Android", deviceType: .mobile, isVerified: false, lastSeenIP: "3.0.0.3", lastSeenTimestamp: (Date().timeIntervalSince1970 - 10)) ] - self.lastOverviewData = UserSessionsOverviewData(currentSessionInfo: currentSessionInfo, unverifiedSessionsInfo: unverifiedSessionsInfo, inactiveSessionsInfo: inactiveSessionsInfo, otherSessionsInfo: otherSessionsInfo) + lastOverviewData = UserSessionsOverviewData(currentSessionInfo: currentSessionInfo, + unverifiedSessionsInfo: unverifiedSessionsInfo, + inactiveSessionsInfo: inactiveSessionsInfo, + otherSessionsInfo: otherSessionsInfo) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift index dec3b06df..07478f201 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift @@ -17,7 +17,6 @@ import Foundation struct UserSessionsOverviewData { - let currentSessionInfo: UserSessionInfo? let unverifiedSessionsInfo: [UserSessionInfo] let inactiveSessionsInfo: [UserSessionInfo] @@ -25,7 +24,6 @@ struct UserSessionsOverviewData { } protocol UserSessionsOverviewServiceProtocol { - var lastOverviewData: UserSessionsOverviewData { get } func fetchUserSessionsOverviewData(completion: @escaping (Result) -> Void) -> Void diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionsOverviewViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionsOverviewViewModelTests.swift index d45804590..d9c2ecb9c 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionsOverviewViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionsOverviewViewModelTests.swift @@ -20,7 +20,6 @@ import Combine @testable import RiotSwiftUI class UserSessionsOverviewViewModelTests: XCTestCase { - var service: MockUserSessionsOverviewService! var viewModel: UserSessionsOverviewViewModelProtocol! var context: UserSessionsOverviewViewModelType.Context! diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift index dd27e0c63..8e66bd0e2 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift @@ -36,14 +36,13 @@ enum UserSessionsOverviewViewModelResult { // MARK: View struct UserSessionsOverviewViewState: BindableState { - - var unverifiedSessionsViewData: [UserSessionListItemViewData] - - var inactiveSessionsViewData: [UserSessionListItemViewData] - var currentSessionViewData: UserSessionCardViewData? - var otherSessionsViewData: [UserSessionListItemViewData] + var unverifiedSessionsViewData = [UserSessionListItemViewData]() + + var inactiveSessionsViewData = [UserSessionListItemViewData]() + + var otherSessionsViewData = [UserSessionListItemViewData]() var showLoadingIndicator: Bool = false } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift index b3e185d86..3d51884fc 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift @@ -21,91 +21,63 @@ typealias UserSessionsOverviewViewModelType = StateStoreViewModel class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSessionsOverviewViewModelProtocol { - - // MARK: - Properties - - // MARK: Private - private let userSessionsOverviewService: UserSessionsOverviewServiceProtocol - // MARK: Public - var completion: ((UserSessionsOverviewViewModelResult) -> Void)? - // MARK: - Setup - init(userSessionsOverviewService: UserSessionsOverviewServiceProtocol) { self.userSessionsOverviewService = userSessionsOverviewService - let initialViewState = UserSessionsOverviewViewState(unverifiedSessionsViewData: [], inactiveSessionsViewData: [], currentSessionViewData: nil, otherSessionsViewData: []) + super.init(initialViewState: .init()) - super.init(initialViewState: initialViewState) - - self.updateViewState(with: userSessionsOverviewService.lastOverviewData) + updateViewState(with: userSessionsOverviewService.lastOverviewData) } // MARK: - Public - + override func process(viewAction: UserSessionsOverviewViewAction) { switch viewAction { case .viewAppeared: - self.loadData() + loadData() case .verifyCurrentSession: - self.completion?(.verifyCurrentSession) + completion?(.verifyCurrentSession) case .viewCurrentSessionDetails: guard let currentSessionInfo = userSessionsOverviewService.lastOverviewData.currentSessionInfo else { assertionFailure("currentSessionInfo should be present") - return + return } - self.completion?(.showCurrentSessionOverview(sessionInfo: currentSessionInfo)) + completion?(.showCurrentSessionOverview(sessionInfo: currentSessionInfo)) case .viewAllUnverifiedSessions: - self.completion?(.showAllUnverifiedSessions) + completion?(.showAllUnverifiedSessions) case .viewAllInactiveSessions: - self.completion?(.showAllInactiveSessions) + completion?(.showAllInactiveSessions) case .viewAllOtherSessions: - self.completion?(.showAllOtherSessions) + completion?(.showAllOtherSessions) case .tapUserSession(let sessionId): guard let sessionInfo = userSessionsOverviewService.getOtherSession(sessionId: sessionId) else { assertionFailure("missing session info") return } - self.completion?(.showUserSessionOverview(sessionInfo: sessionInfo)) + completion?(.showUserSessionOverview(sessionInfo: sessionInfo)) } } // MARK: - Private private func updateViewState(with userSessionsViewData: UserSessionsOverviewData) { - - let unverifiedSessionsViewData = self.userSessionListItemViewDataList(from: userSessionsViewData.unverifiedSessionsInfo) - let inactiveSessionsViewData = self.userSessionListItemViewDataList(from: userSessionsViewData.inactiveSessionsInfo) - - var currentSessionViewData: UserSessionCardViewData? - - let otherSessionsViewData = self.userSessionListItemViewDataList(from: userSessionsViewData.otherSessionsInfo) - + state.unverifiedSessionsViewData = userSessionsViewData.unverifiedSessionsInfo.asViewData() + state.inactiveSessionsViewData = userSessionsViewData.inactiveSessionsInfo.asViewData() + state.otherSessionsViewData = userSessionsViewData.otherSessionsInfo.asViewData() if let currentSessionInfo = userSessionsViewData.currentSessionInfo { - currentSessionViewData = UserSessionCardViewData(userSessionInfo: currentSessionInfo, isCurrentSessionDisplayMode: true) - } - - self.state.unverifiedSessionsViewData = unverifiedSessionsViewData - self.state.inactiveSessionsViewData = inactiveSessionsViewData - self.state.currentSessionViewData = currentSessionViewData - self.state.otherSessionsViewData = otherSessionsViewData - } - - private func userSessionListItemViewDataList(from userSessionInfoList: [UserSessionInfo]) -> [UserSessionListItemViewData] { - return userSessionInfoList.map { - return UserSessionListItemViewData(userSessionInfo: $0) + state.currentSessionViewData = UserSessionCardViewData(userSessionInfo: currentSessionInfo, isCurrentSessionDisplayMode: true) } } private func loadData() { + state.showLoadingIndicator = true - self.state.showLoadingIndicator = true - - self.userSessionsOverviewService.fetchUserSessionsOverviewData { [weak self] result in + userSessionsOverviewService.fetchUserSessionsOverviewData { [weak self] result in guard let self = self else { return } @@ -122,3 +94,9 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess } } } + +private extension Collection where Element == UserSessionInfo { + func asViewData() -> [UserSessionListItemViewData] { + map { UserSessionListItemViewData(userSessionInfo: $0) } + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModelProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModelProtocol.swift index f416bb16a..ebbb790a9 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModelProtocol.swift @@ -17,7 +17,6 @@ import Foundation protocol UserSessionsOverviewViewModelProtocol { - var completion: ((UserSessionsOverviewViewModelResult) -> Void)? { get set } var context: UserSessionsOverviewViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift index 7342764c4..f107c85f0 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift @@ -17,9 +17,6 @@ import SwiftUI struct UserSessionListItem: View { - - // MARK: - Constants - private enum LayoutConstants { static let horizontalPadding: CGFloat = 15 static let verticalPadding: CGFloat = 16 @@ -27,23 +24,16 @@ struct UserSessionListItem: View { static let avatarRightMargin: CGFloat = 18 } - // MARK: - Properties - - // MARK: Private - @Environment(\.theme) private var theme: ThemeSwiftUI - // MARK: Public - let viewData: UserSessionListItemViewData var onBackgroundTap: ((String) -> (Void))? = nil - // MARK: - Body - var body: some View { - Button(action: { onBackgroundTap?(self.viewData.sessionId) - }) { + Button { + onBackgroundTap?(viewData.sessionId) + } label: { VStack(alignment: .leading, spacing: LayoutConstants.verticalPadding) { HStack(spacing: LayoutConstants.avatarRightMargin) { DeviceAvatarView(viewData: viewData.deviceAvatarViewData) @@ -74,7 +64,6 @@ struct UserSessionListItem: View { } struct UserSessionListPreview: View { - let userSessionsOverviewService: UserSessionsOverviewServiceProtocol = MockUserSessionsOverviewService() var body: some View { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift index 4acac069e..a682cd8e4 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift @@ -18,14 +18,9 @@ import Foundation /// View data for UserSessionListItem struct UserSessionListItemViewData: Identifiable { - - // MARK: - Constants - private static let userSessionNameFormatter = UserSessionNameFormatter() private static let lastActivityDateFormatter = UserSessionLastActivityFormatter() - - // MARK: - Properties - + var id: String { return sessionId } @@ -38,8 +33,6 @@ struct UserSessionListItemViewData: Identifiable { let deviceAvatarViewData: DeviceAvatarViewData - // MARK: - Setup - init(sessionId: String, sessionDisplayName: String?, deviceType: DeviceType, @@ -47,15 +40,14 @@ struct UserSessionListItemViewData: Identifiable { lastActivityDate: TimeInterval?) { self.sessionId = sessionId - self.sessionName = Self.userSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName) - self.sessionDetails = Self.buildSessionDetails(isVerified: isVerified, lastActivityDate: lastActivityDate) - self.deviceAvatarViewData = DeviceAvatarViewData(deviceType: deviceType, isVerified: isVerified) + sessionName = Self.userSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName) + sessionDetails = Self.buildSessionDetails(isVerified: isVerified, lastActivityDate: lastActivityDate) + deviceAvatarViewData = DeviceAvatarViewData(deviceType: deviceType, isVerified: isVerified) } // MARK: - Private private static func buildSessionDetails(isVerified: Bool, lastActivityDate: TimeInterval?) -> String { - let sessionDetailsString: String let sessionStatusText = isVerified ? VectorL10n.userSessionVerifiedShort : VectorL10n.userSessionUnverifiedShort @@ -77,7 +69,6 @@ struct UserSessionListItemViewData: Identifiable { } extension UserSessionListItemViewData { - init(userSessionInfo: UserSessionInfo) { self.init(sessionId: userSessionInfo.sessionId, sessionDisplayName: userSessionInfo.sessionName, deviceType: userSessionInfo.deviceType, isVerified: userSessionInfo.isVerified, lastActivityDate: userSessionInfo.lastSeenTimestamp) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift index 27601502f..565718d69 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift @@ -17,11 +17,6 @@ import SwiftUI struct UserSessionsOverview: View { - - // MARK: - Properties - - // MARK: Private - @Environment(\.theme) private var theme: ThemeSwiftUI @ViewBuilder @@ -52,7 +47,6 @@ struct UserSessionsOverview: View { var body: some View { ScrollView { - // Security recommendations section if viewModel.viewState.unverifiedSessionsViewData.isEmpty == false || viewModel.viewState.inactiveSessionsViewData.isEmpty == false { @@ -64,7 +58,7 @@ struct UserSessionsOverview: View { // Other sessions section if viewModel.viewState.otherSessionsViewData.isEmpty == false { - self.otherSessionsSection + otherSessionsSection } } .background(theme.colors.system.ignoresSafeArea()) @@ -77,7 +71,6 @@ struct UserSessionsOverview: View { } private var otherSessionsSection: some View { - SwiftUI.Section { // Device list LazyVStack(spacing: 0) { From aca98de74c00c7f5398b4451cc3c2bd9b8907028 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 26 Sep 2022 18:28:17 +0300 Subject: [PATCH 058/104] Move files around, improve structure --- .../Modules/UserSessions/{DeviceType => Common}/DeviceType.swift | 0 .../{UserSessionInfo => Common}/UserSessionInfo.swift | 0 .../UserSessionLastActivityFormatter.swift | 0 .../UserSessionNameFormatter.swift | 0 .../{DeviceAvatar => Common/View}/DeviceAvatarView.swift | 0 .../{DeviceAvatar => Common/View}/DeviceAvatarViewData.swift | 0 .../Modules/UserSessions/Common/{ => View}/SeparatorLine.swift | 0 .../View}/UserSessionCardView.swift | 0 .../View}/UserSessionCardViewData.swift | 0 .../Coordinator/UserSessionsFlowCoordinator.swift | 0 .../Coordinator/UserSessionsFlowCoordinatorBridgePresenter.swift | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename RiotSwiftUI/Modules/UserSessions/{DeviceType => Common}/DeviceType.swift (100%) rename RiotSwiftUI/Modules/UserSessions/{UserSessionInfo => Common}/UserSessionInfo.swift (100%) rename RiotSwiftUI/Modules/UserSessions/{UserSessionFormatters => Common}/UserSessionLastActivityFormatter.swift (100%) rename RiotSwiftUI/Modules/UserSessions/{UserSessionFormatters => Common}/UserSessionNameFormatter.swift (100%) rename RiotSwiftUI/Modules/UserSessions/{DeviceAvatar => Common/View}/DeviceAvatarView.swift (100%) rename RiotSwiftUI/Modules/UserSessions/{DeviceAvatar => Common/View}/DeviceAvatarViewData.swift (100%) rename RiotSwiftUI/Modules/UserSessions/Common/{ => View}/SeparatorLine.swift (100%) rename RiotSwiftUI/Modules/UserSessions/{UserSessionCardView => Common/View}/UserSessionCardView.swift (100%) rename RiotSwiftUI/Modules/UserSessions/{UserSessionCardView => Common/View}/UserSessionCardViewData.swift (100%) rename RiotSwiftUI/Modules/UserSessions/{UserSessionsFlow => }/Coordinator/UserSessionsFlowCoordinator.swift (100%) rename RiotSwiftUI/Modules/UserSessions/{UserSessionsFlow => }/Coordinator/UserSessionsFlowCoordinatorBridgePresenter.swift (100%) diff --git a/RiotSwiftUI/Modules/UserSessions/DeviceType/DeviceType.swift b/RiotSwiftUI/Modules/UserSessions/Common/DeviceType.swift similarity index 100% rename from RiotSwiftUI/Modules/UserSessions/DeviceType/DeviceType.swift rename to RiotSwiftUI/Modules/UserSessions/Common/DeviceType.swift diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionInfo/UserSessionInfo.swift b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift similarity index 100% rename from RiotSwiftUI/Modules/UserSessions/UserSessionInfo/UserSessionInfo.swift rename to RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionFormatters/UserSessionLastActivityFormatter.swift b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionLastActivityFormatter.swift similarity index 100% rename from RiotSwiftUI/Modules/UserSessions/UserSessionFormatters/UserSessionLastActivityFormatter.swift rename to RiotSwiftUI/Modules/UserSessions/Common/UserSessionLastActivityFormatter.swift diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionFormatters/UserSessionNameFormatter.swift b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionNameFormatter.swift similarity index 100% rename from RiotSwiftUI/Modules/UserSessions/UserSessionFormatters/UserSessionNameFormatter.swift rename to RiotSwiftUI/Modules/UserSessions/Common/UserSessionNameFormatter.swift diff --git a/RiotSwiftUI/Modules/UserSessions/DeviceAvatar/DeviceAvatarView.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/DeviceAvatarView.swift similarity index 100% rename from RiotSwiftUI/Modules/UserSessions/DeviceAvatar/DeviceAvatarView.swift rename to RiotSwiftUI/Modules/UserSessions/Common/View/DeviceAvatarView.swift diff --git a/RiotSwiftUI/Modules/UserSessions/DeviceAvatar/DeviceAvatarViewData.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/DeviceAvatarViewData.swift similarity index 100% rename from RiotSwiftUI/Modules/UserSessions/DeviceAvatar/DeviceAvatarViewData.swift rename to RiotSwiftUI/Modules/UserSessions/Common/View/DeviceAvatarViewData.swift diff --git a/RiotSwiftUI/Modules/UserSessions/Common/SeparatorLine.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/SeparatorLine.swift similarity index 100% rename from RiotSwiftUI/Modules/UserSessions/Common/SeparatorLine.swift rename to RiotSwiftUI/Modules/UserSessions/Common/View/SeparatorLine.swift diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardView.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift similarity index 100% rename from RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardView.swift rename to RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardViewData.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift similarity index 100% rename from RiotSwiftUI/Modules/UserSessions/UserSessionCardView/UserSessionCardViewData.swift rename to RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift similarity index 100% rename from RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift rename to RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinatorBridgePresenter.swift b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinatorBridgePresenter.swift similarity index 100% rename from RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinatorBridgePresenter.swift rename to RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinatorBridgePresenter.swift From 205e1645d4433e92e7e3d7c41f3aaf5a4a55120a Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 26 Sep 2022 20:27:06 +0300 Subject: [PATCH 059/104] Cleaned up various things around the UserSessionsOverviewService --- .../UserSessionsOverviewService.swift | 115 +++++++++--------- .../MockUserSessionsOverviewService.swift | 16 +-- .../UserSessionsOverviewServiceProtocol.swift | 16 +-- .../UserSessionsOverviewViewModel.swift | 20 +-- .../View/UserSessionListItem.swift | 2 +- 5 files changed, 85 insertions(+), 84 deletions(-) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift index ab242d906..2844b2269 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift @@ -20,59 +20,97 @@ import MatrixSDK class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { private let mxSession: MXSession - private(set) var lastOverviewData: UserSessionsOverviewData + private(set) var overviewData: UserSessionsOverviewData init(mxSession: MXSession) { self.mxSession = mxSession - lastOverviewData = UserSessionsOverviewData(currentSessionInfo: nil, - unverifiedSessionsInfo: [], - inactiveSessionsInfo: [], - otherSessionsInfo: []) + overviewData = UserSessionsOverviewData(currentSession: nil, + unverifiedSessions: [], + inactiveSessions: [], + otherSessions: []) setupInitialOverviewData() } // MARK: - Public - func fetchUserSessionsOverviewData(completion: @escaping (Result) -> Void) { + func updateOverviewData(completion: @escaping (Result) -> Void) { mxSession.matrixRestClient.devices { response in switch response { case .success(let devices): - self.lastOverviewData = self.userSessionsOverviewData(from: devices) - completion(.success(self.lastOverviewData)) + self.overviewData = self.sessionsOverviewData(from: devices) + completion(.success(self.overviewData)) case .failure(let error): completion(.failure(error)) } } } - func getOtherSession(sessionId: String) -> UserSessionInfo? { - lastOverviewData.otherSessionsInfo.first(where: {$0.sessionId == sessionId}) + func sessionForIdentifier(_ sessionId: String) -> UserSessionInfo? { + if overviewData.currentSession?.id == sessionId { + return overviewData.currentSession + } + + return overviewData.otherSessions.first(where: { $0.sessionId == sessionId }) } // MARK: - Private private func setupInitialOverviewData() { - let currentSessionInfo = getCurrentUserSessionInfoFromCache() + let currentSessionInfo = currentSessionInfo() - lastOverviewData = UserSessionsOverviewData(currentSessionInfo: currentSessionInfo, unverifiedSessionsInfo: [], inactiveSessionsInfo: [], otherSessionsInfo: []) + overviewData = UserSessionsOverviewData(currentSession: currentSessionInfo, + unverifiedSessions: [], + inactiveSessions: [], + otherSessions: []) } - private func getCurrentUserSessionInfoFromCache() -> UserSessionInfo? { - guard let mainAccount = MXKAccountManager.shared().activeAccounts.first, let device = mainAccount.device else { + private func currentSessionInfo() -> UserSessionInfo? { + guard let mainAccount = MXKAccountManager.shared().activeAccounts.first, + let device = mainAccount.device else { return nil } - return userSessionInfo(from: device) + return sessionInfo(from: device) } - private func userSessionInfo(from device: MXDevice) -> UserSessionInfo { - let deviceInfo = getDeviceInfo(for: device.deviceId) + private func sessionsOverviewData(from devices: [MXDevice]) -> UserSessionsOverviewData { + let allSessions = devices + .sorted { $0.lastSeenTs > $1.lastSeenTs } + .map { sessionInfo(from: $0) } - let isSessionVerified = deviceInfo?.trustLevel.isVerified ?? false + var currentSession: UserSessionInfo? + var unverifiedSessions: [UserSessionInfo] = [] + var inactiveSessions: [UserSessionInfo] = [] + var otherSessions: [UserSessionInfo] = [] + + for session in allSessions { + guard session.sessionId != mxSession.myDeviceId else { + currentSession = session + continue + } + + otherSessions.append(session) + + if session.isVerified == false { + unverifiedSessions.append(session) + } + + if session.isSessionActive == false { + inactiveSessions.append(session) + } + } + + return UserSessionsOverviewData(currentSession: currentSession, + unverifiedSessions: unverifiedSessions, + inactiveSessions: inactiveSessions, + otherSessions: otherSessions) + } + + private func sessionInfo(from device: MXDevice) -> UserSessionInfo { + let isSessionVerified = deviceInfo(for: device.deviceId)?.trustLevel.isVerified ?? false var lastSeenTs: TimeInterval? - if device.lastSeenTs > 0 { lastSeenTs = TimeInterval(device.lastSeenTs / 1000) } @@ -85,48 +123,11 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { lastSeenTimestamp: lastSeenTs) } - private func getDeviceInfo(for deviceId: String) -> MXDeviceInfo? { + private func deviceInfo(for deviceId: String) -> MXDeviceInfo? { guard let userId = mxSession.myUserId else { return nil } return mxSession.crypto.device(withDeviceId: deviceId, ofUser: userId) } - - private func userSessionsOverviewData(from devices: [MXDevice]) -> UserSessionsOverviewData { - let sortedDevices = devices.sorted { device1, device2 in - device1.lastSeenTs > device2.lastSeenTs - } - - let allUserSessionInfo = sortedDevices.map { device in - return userSessionInfo(from: device) - } - - var currentSessionInfo: UserSessionInfo? - - var unverifiedSessionsInfo: [UserSessionInfo] = [] - var inactiveSessionsInfo: [UserSessionInfo] = [] - var otherSessionsInfo: [UserSessionInfo] = [] - - for userSessionInfo in allUserSessionInfo { - if userSessionInfo.sessionId == mxSession.myDeviceId { - currentSessionInfo = userSessionInfo - } else { - otherSessionsInfo.append(userSessionInfo) - - if userSessionInfo.isVerified == false { - unverifiedSessionsInfo.append(userSessionInfo) - } - - if userSessionInfo.isSessionActive == false { - inactiveSessionsInfo.append(userSessionInfo) - } - } - } - - return UserSessionsOverviewData(currentSessionInfo: currentSessionInfo, - unverifiedSessionsInfo: unverifiedSessionsInfo, - inactiveSessionsInfo: inactiveSessionsInfo, - otherSessionsInfo: otherSessionsInfo) - } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift index 34d79fe16..89b8d3aa3 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift @@ -17,13 +17,13 @@ import Foundation class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { - var lastOverviewData: UserSessionsOverviewData + var overviewData: UserSessionsOverviewData - func fetchUserSessionsOverviewData(completion: @escaping (Result) -> Void) { - completion(.success(lastOverviewData)) + func updateOverviewData(completion: @escaping (Result) -> Void) { + completion(.success(overviewData)) } - func getOtherSession(sessionId: String) -> UserSessionInfo? { + func sessionForIdentifier(_ sessionId: String) -> UserSessionInfo? { nil } @@ -40,9 +40,9 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { UserSessionInfo(sessionId: "3", sessionName: "Android", deviceType: .mobile, isVerified: false, lastSeenIP: "3.0.0.3", lastSeenTimestamp: (Date().timeIntervalSince1970 - 10)) ] - lastOverviewData = UserSessionsOverviewData(currentSessionInfo: currentSessionInfo, - unverifiedSessionsInfo: unverifiedSessionsInfo, - inactiveSessionsInfo: inactiveSessionsInfo, - otherSessionsInfo: otherSessionsInfo) + overviewData = UserSessionsOverviewData(currentSession: currentSessionInfo, + unverifiedSessions: unverifiedSessionsInfo, + inactiveSessions: inactiveSessionsInfo, + otherSessions: otherSessionsInfo) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift index 07478f201..9b3e81706 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift @@ -17,16 +17,16 @@ import Foundation struct UserSessionsOverviewData { - let currentSessionInfo: UserSessionInfo? - let unverifiedSessionsInfo: [UserSessionInfo] - let inactiveSessionsInfo: [UserSessionInfo] - let otherSessionsInfo: [UserSessionInfo] + let currentSession: UserSessionInfo? + let unverifiedSessions: [UserSessionInfo] + let inactiveSessions: [UserSessionInfo] + let otherSessions: [UserSessionInfo] } protocol UserSessionsOverviewServiceProtocol { - var lastOverviewData: UserSessionsOverviewData { get } - - func fetchUserSessionsOverviewData(completion: @escaping (Result) -> Void) -> Void + var overviewData: UserSessionsOverviewData { get } - func getOtherSession(sessionId: String) -> UserSessionInfo? + func updateOverviewData(completion: @escaping (Result) -> Void) -> Void + + func sessionForIdentifier(_ sessionId: String) -> UserSessionInfo? } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift index 3d51884fc..855cc2e19 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift @@ -30,7 +30,7 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess super.init(initialViewState: .init()) - updateViewState(with: userSessionsOverviewService.lastOverviewData) + updateViewState(with: userSessionsOverviewService.overviewData) } // MARK: - Public @@ -42,8 +42,8 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess case .verifyCurrentSession: completion?(.verifyCurrentSession) case .viewCurrentSessionDetails: - guard let currentSessionInfo = userSessionsOverviewService.lastOverviewData.currentSessionInfo else { - assertionFailure("currentSessionInfo should be present") + guard let currentSessionInfo = userSessionsOverviewService.overviewData.currentSession else { + assertionFailure("Missing current session") return } completion?(.showCurrentSessionOverview(sessionInfo: currentSessionInfo)) @@ -54,8 +54,8 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess case .viewAllOtherSessions: completion?(.showAllOtherSessions) case .tapUserSession(let sessionId): - guard let sessionInfo = userSessionsOverviewService.getOtherSession(sessionId: sessionId) else { - assertionFailure("missing session info") + guard let sessionInfo = userSessionsOverviewService.sessionForIdentifier(sessionId) else { + assertionFailure("Missing session info") return } completion?(.showUserSessionOverview(sessionInfo: sessionInfo)) @@ -65,11 +65,11 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess // MARK: - Private private func updateViewState(with userSessionsViewData: UserSessionsOverviewData) { - state.unverifiedSessionsViewData = userSessionsViewData.unverifiedSessionsInfo.asViewData() - state.inactiveSessionsViewData = userSessionsViewData.inactiveSessionsInfo.asViewData() - state.otherSessionsViewData = userSessionsViewData.otherSessionsInfo.asViewData() + state.unverifiedSessionsViewData = userSessionsViewData.unverifiedSessions.asViewData() + state.inactiveSessionsViewData = userSessionsViewData.inactiveSessions.asViewData() + state.otherSessionsViewData = userSessionsViewData.otherSessions.asViewData() - if let currentSessionInfo = userSessionsViewData.currentSessionInfo { + if let currentSessionInfo = userSessionsViewData.currentSession { state.currentSessionViewData = UserSessionCardViewData(userSessionInfo: currentSessionInfo, isCurrentSessionDisplayMode: true) } } @@ -77,7 +77,7 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess private func loadData() { state.showLoadingIndicator = true - userSessionsOverviewService.fetchUserSessionsOverviewData { [weak self] result in + userSessionsOverviewService.updateOverviewData { [weak self] result in guard let self = self else { return } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift index f107c85f0..e60a366f3 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift @@ -68,7 +68,7 @@ struct UserSessionListPreview: View { var body: some View { VStack(alignment: .leading, spacing: 0) { - ForEach(userSessionsOverviewService.lastOverviewData.otherSessionsInfo) { userSessionInfo in + ForEach(userSessionsOverviewService.overviewData.otherSessions) { userSessionInfo in let viewData = UserSessionListItemViewData(userSessionInfo: userSessionInfo) UserSessionListItem(viewData: viewData, onBackgroundTap: { sessionId in From 89bcd92ccae37bfbad7373485e03b80b2cdeffdb Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 27 Sep 2022 10:17:22 +0300 Subject: [PATCH 060/104] Configured and applied SwiftFormat --- .swiftformat | 15 +++ .../AnalyticsPromptModels.swift | 2 +- .../AnalyticsPromptViewModel.swift | 7 +- .../AnalyticsPromptCoordinator.swift | 7 +- .../Coordinator/AnalyticsPromptStrings.swift | 3 +- .../MockAnalyticsPromptScreenState.swift | 2 +- .../MockAnalyticsPromptStrings.swift | 2 +- .../Test/UI/AnalyticsPromptUITests.swift | 2 +- .../View/AnalyticsPrompt.swift | 3 +- .../View/AnalyticsPromptCheckmarkItem.swift | 10 +- .../AuthenticationChoosePasswordModels.swift | 2 +- ...uthenticationChoosePasswordViewModel.swift | 5 +- ...ationChoosePasswordViewModelProtocol.swift | 3 +- ...henticationChoosePasswordCoordinator.swift | 5 +- ...henticationChoosePasswordScreenState.swift | 4 +- .../AuthenticationChoosePasswordUITests.swift | 3 +- ...ticationChoosePasswordViewModelTests.swift | 4 +- .../AuthenticationChoosePasswordScreen.swift | 5 +- .../AuthenticationHomeserverViewData.swift | 13 +-- .../Common/AuthenticationModels.swift | 4 +- .../Common/AuthenticationSSOButton.swift | 3 +- .../AuthenticationServerInfoSection.swift | 3 +- .../MatrixSDK/AuthenticationPendingData.swift | 2 +- .../MatrixSDK/AuthenticationRestClient.swift | 12 +- .../MatrixSDK/AuthenticationService.swift | 15 ++- .../MatrixSDK/AuthenticationState.swift | 6 +- .../Service/MatrixSDK/LoginModels.swift | 3 +- .../Service/MatrixSDK/LoginParameters.swift | 4 +- .../Service/MatrixSDK/LoginWizard.swift | 4 +- .../MatrixSDK/RegistrationModels.swift | 4 +- .../MatrixSDK/RegistrationParameters.swift | 2 +- .../MatrixSDK/RegistrationWizard.swift | 7 +- .../Service/MatrixSDK/SessionCreator.swift | 5 +- .../Service/MatrixSDK/ThreePIDModels.swift | 10 +- .../AuthenticationForgotPasswordModels.swift | 2 +- ...uthenticationForgotPasswordViewModel.swift | 5 +- ...ationForgotPasswordViewModelProtocol.swift | 3 +- ...henticationForgotPasswordCoordinator.swift | 5 +- ...henticationForgotPasswordScreenState.swift | 4 +- .../AuthenticationForgotPasswordUITests.swift | 3 +- ...ticationForgotPasswordViewModelTests.swift | 3 +- .../AuthenticationForgotPasswordForm.swift | 3 +- .../AuthenticationForgotPasswordScreen.swift | 3 +- .../Login/AuthenticationLoginModels.swift | 2 +- .../Login/AuthenticationLoginViewModel.swift | 7 +- ...AuthenticationLoginViewModelProtocol.swift | 3 +- .../AuthenticationLoginCoordinator.swift | 4 +- .../MockAuthenticationLoginScreenState.swift | 5 +- .../Test/UI/AuthenticationLoginUITests.swift | 3 +- .../AuthenticationLoginViewModelTests.swift | 2 +- .../View/AuthenticationLoginScreen.swift | 12 +- .../AuthenticationReCaptchaModels.swift | 2 +- .../AuthenticationReCaptchaViewModel.swift | 5 +- ...enticationReCaptchaViewModelProtocol.swift | 3 +- .../AuthenticationReCaptchaCoordinator.swift | 5 +- ...ckAuthenticationReCaptchaScreenState.swift | 4 +- .../UI/AuthenticationReCaptchaUITests.swift | 2 +- ...uthenticationReCaptchaViewModelTests.swift | 2 +- .../View/AuthenticationReCaptchaScreen.swift | 3 +- .../View/AuthenticationReCaptchaWebView.swift | 6 +- .../AuthenticationRegistrationModels.swift | 4 +- .../AuthenticationRegistrationViewModel.swift | 7 +- ...icationRegistrationViewModelProtocol.swift | 3 +- ...uthenticationRegistrationCoordinator.swift | 8 +- ...uthenticationRegistrationScreenState.swift | 5 +- .../AuthenticationRegistrationUITests.swift | 3 +- ...enticationRegistrationViewModelTests.swift | 8 +- .../AuthenticationRegistrationScreen.swift | 14 +-- .../AuthenticationServerSelectionModels.swift | 2 +- ...thenticationServerSelectionViewModel.swift | 5 +- ...tionServerSelectionViewModelProtocol.swift | 3 +- ...enticationServerSelectionCoordinator.swift | 5 +- ...enticationServerSelectionScreenState.swift | 4 +- ...AuthenticationServerSelectionUITests.swift | 3 +- ...icationServerSelectionViewModelTests.swift | 2 +- .../AuthenticationServerSelectionScreen.swift | 5 +- .../AuthenticationSoftLogoutModels.swift | 3 +- .../AuthenticationSoftLogoutViewModel.swift | 5 +- ...nticationSoftLogoutViewModelProtocol.swift | 3 +- .../AuthenticationSoftLogoutCoordinator.swift | 5 +- ...kAuthenticationSoftLogoutScreenState.swift | 4 +- .../UI/AuthenticationSoftLogoutUITests.swift | 3 +- ...thenticationSoftLogoutViewModelTests.swift | 4 +- .../View/AuthenticationSoftLogoutScreen.swift | 6 +- .../Terms/AuthenticationTermsModels.swift | 4 +- .../Terms/AuthenticationTermsViewModel.swift | 6 +- ...AuthenticationTermsViewModelProtocol.swift | 3 +- .../AuthenticationTermsCoordinator.swift | 5 +- .../MockAuthenticationTermsScreenState.swift | 4 +- .../Test/UI/AuthenticationTermsUITests.swift | 3 +- .../AuthenticationTermsViewModelTests.swift | 2 +- .../View/AuthenticationTermsListItem.swift | 3 +- .../View/AuthenticationTermsScreen.swift | 3 +- .../View/AuthenticationTermsToggleStyle.swift | 2 +- .../AuthenticationVerifyEmailModels.swift | 2 +- .../AuthenticationVerifyEmailViewModel.swift | 5 +- ...ticationVerifyEmailViewModelProtocol.swift | 3 +- ...AuthenticationVerifyEmailCoordinator.swift | 5 +- ...AuthenticationVerifyEmailScreenState.swift | 4 +- .../UI/AuthenticationVerifyEmailUITests.swift | 3 +- ...henticationVerifyEmailViewModelTests.swift | 3 +- .../View/AuthenticationVerifyEmailForm.swift | 3 +- .../AuthenticationVerifyEmailScreen.swift | 3 +- .../AuthenticationVerifyMsisdnModels.swift | 2 +- .../AuthenticationVerifyMsisdnViewModel.swift | 5 +- ...icationVerifyMsisdnViewModelProtocol.swift | 3 +- ...uthenticationVerifyMsisdnCoordinator.swift | 6 +- ...uthenticationVerifyMsisdnScreenState.swift | 4 +- .../AuthenticationVerifyMsisdnUITests.swift | 3 +- ...enticationVerifyMsisdnViewModelTests.swift | 3 +- .../View/AuthenticationVerifyMsisdnForm.swift | 5 +- .../AuthenticationVerifyMsisdnOTPForm.swift | 9 +- .../AuthenticationVerifyMsisdnScreen.swift | 5 +- .../ActivityIndicator/ActivityIndicator.swift | 4 +- .../ActivityIndicatorModifier.swift | 4 +- .../Avatar/Model/AvatarInputProtocol.swift | 2 +- .../Common/Avatar/Model/AvatarProtocol.swift | 2 +- .../Common/Avatar/Model/Avatarable.swift | 4 +- .../Avatar/Model/Mock/MockAvatarInput.swift | 2 +- .../Service/MatrixSDK/AvatarService.swift | 16 +-- .../Service/Mock/MockAvatarService.swift | 4 +- .../Common/Avatar/View/AvatarImage.swift | 7 +- .../Avatar/View/PlaceholderAvatarImage.swift | 3 +- .../Common/Avatar/View/SpaceAvatarImage.swift | 3 +- .../ViewModel/AvatarServiceProtocol.swift | 7 +- .../Avatar/ViewModel/AvatarViewModel.swift | 21 ++-- .../Avatar/ViewModel/AvatarViewState.swift | 2 +- .../PlaceholderAvatarViewModel.swift | 6 +- .../Common/Bridging/VectorContentView.swift | 5 +- .../DependencyContainer.swift | 5 +- .../DependencyContainerKey.swift | 1 - .../Common/DependencyInjection/Inject.swift | 15 +-- .../DependencyInjection/Injectable.swift | 4 +- .../InjectableObject.swift | 2 +- .../Common/EffectsScene/EffectsScene.swift | 9 +- .../Common/EffectsScene/EffectsView.swift | 5 +- .../Common/ErrorHandling/AlertInfo.swift | 8 +- .../Extensions/CLLocationCoordinate2D.swift | 8 +- .../MatrixSDK/MXRoomAvatarable.swift | 3 +- .../MatrixSDK/MXUserAvatarable.swift | 3 +- .../Modules/Common/Extensions/Publisher.swift | 10 +- .../Common/Locale/LocaleProvider.swift | 4 +- .../Common/Logging/LoggerProtocol.swift | 2 +- .../Modules/Common/Logging/PrintLogger.swift | 6 +- .../Modules/Common/Logging/UILog.swift | 58 +++++---- .../Modules/Common/Mock/MockAppScreens.swift | 3 +- .../Modules/Common/Mock/MockScreenState.swift | 15 ++- .../Modules/Common/Mock/ScreenList.swift | 5 +- .../Modules/Common/Mock/ScreenStateInfo.swift | 2 +- .../Modules/Common/Mock/StateRenderer.swift | 6 +- .../Common/Test/UI/MockScreenTest.swift | 6 +- .../Common/Test/UI/XCUIApplication+Riot.swift | 8 +- .../Test/XCTestPublisherExtensions.swift | 20 ++-- .../Theme/ThemeIdentifierExtensions.swift | 4 +- .../Modules/Common/Theme/ThemeKey.swift | 24 ++-- .../Modules/Common/Theme/ThemePublisher.swift | 17 ++- .../Modules/Common/Theme/ThemeSwiftUI.swift | 4 +- .../Theme/ThemeUsersColorsExtension.swift | 3 +- .../Theme/Themes/DarkThemeSwiftUI.swift | 8 +- .../Theme/Themes/DefaultThemeSwiftUI.swift | 8 +- .../Modules/Common/Util/BorderModifier.swift | 3 +- .../Common/Util/BorderedInputFieldStyle.swift | 17 ++- .../Common/Util/ClearViewModifier.swift | 4 +- .../Common/Util/InlineTextButton.swift | 10 +- .../Common/Util/MultilineTextField.swift | 28 +++-- .../Modules/Common/Util/OptionButton.swift | 19 ++- .../Common/Util/PasswordButtonModifier.swift | 1 - .../Util/PrimaryActionButtonStyle.swift | 6 +- .../Modules/Common/Util/RadioButton.swift | 7 +- .../Common/Util/ReadableFrameModifier.swift | 4 +- .../Common/Util/ResponderManager.swift | 5 +- .../Common/Util/RoundedBorderTextEditor.swift | 18 ++- .../Common/Util/RoundedBorderTextField.swift | 20 ++-- .../Common/Util/RoundedCornerShape.swift | 3 +- .../SafeBindingCollectionEnumerator.swift | 3 +- .../Util/ScreenTrackerViewModifier.swift | 4 +- .../Modules/Common/Util/SearchBar.swift | 5 +- .../Util/SecondaryActionButtonStyle.swift | 8 +- .../Modules/Common/Util/StyledText.swift | 14 +-- .../Modules/Common/Util/ThemableButton.swift | 12 +- .../Common/Util/ThemableNavigationBar.swift | 25 ++-- .../Common/Util/ThemableTextEditor.swift | 18 ++- .../Common/Util/ThemableTextField.swift | 29 +++-- .../Modules/Common/Util/View+Riot.swift | 4 +- .../Modules/Common/Util/WaitOverlay.swift | 45 ++++--- .../ViewFrameReader/FramePreferenceKey.swift | 2 +- .../ViewFrameReader/ViewFrameReader.swift | 2 +- .../Common/ViewModel/BindableState.swift | 2 +- .../ViewModel/StateStoreViewModel.swift | 16 ++- .../LiveLocationLabPromotionCoordinator.swift | 8 +- .../LiveLocationLabPromotionModels.swift | 3 +- .../LiveLocationLabPromotionViewModel.swift | 9 +- ...ocationLabPromotionViewModelProtocol.swift | 3 +- ...kLiveLocationLabPromotionScreenState.swift | 5 +- .../UI/LiveLocationLabPromotionUITests.swift | 2 +- ...veLocationLabPromotionViewModelTests.swift | 2 +- .../View/LiveLocationLabPromotionView.swift | 4 +- ...LiveLocationSharingViewerCoordinator.swift | 12 +- .../LiveLocationSharingViewerModels.swift | 15 ++- .../LiveLocationSharingViewerViewModel.swift | 82 ++++++------- ...cationSharingViewerViewModelProtocol.swift | 3 +- ...LiveLocationSharingViewerScreenState.swift | 7 +- ...LocationSharingViewerServiceProtocol.swift | 5 +- .../LiveLocationSharingViewerService.swift | 36 +++--- ...MockLiveLocationSharingViewerService.swift | 30 ++--- .../Service/UserLiveLocation.swift | 9 +- .../UI/LiveLocationSharingViewerUITests.swift | 2 +- ...eLocationSharingViewerViewModelTests.swift | 5 +- .../View/LiveLocationListItem.swift | 46 +++----- .../View/LiveLocationListItemViewData.swift | 5 +- .../View/LiveLocationSharingViewer.swift | 27 +++-- .../MapCredits/MapCreditsActionSheet.swift | 3 +- .../MapCredits/View/MapCreditsView.swift | 3 +- .../MapViewErrorAlertInfoBuilder.swift | 19 ++- .../MapError/View/MapLoadingErrorView.swift | 3 +- .../MapView/LocationAnnotation.swift | 13 +-- .../MapView/View/LocationSharingMapView.swift | 39 +++---- .../View/LocationSharingMarkerView.swift | 3 +- .../UserAnnotationCalloutContentView.swift | 25 ++-- .../View/UserAnnotationCalloutView.swift | 40 +++---- .../View/UserLocationAnnotationView.swift | 22 ++-- .../ShareLocationActivityController.swift | 13 +-- .../ShareToMapsAppActivity.swift | 12 +- .../LocationSharingCoordinator.swift | 49 ++++---- .../LocationSharingModels.swift | 23 ++-- .../LocationSharingScreenState.swift | 9 +- .../LocationSharingViewModel.swift | 37 +++--- .../LocationSharingViewModelProtocol.swift | 2 +- .../LocationSharingServiceProtocol.swift | 5 +- .../MatrixSDK/LocationSharingService.swift | 10 +- .../Mock/MockLocationSharingService.swift | 4 +- .../Test/UI/LocationSharingUITests.swift | 2 +- .../Unit/LocationSharingViewModelTests.swift | 8 +- .../View/LocationSharingOptionButton.swift | 17 +-- .../View/LocationSharingView.swift | 10 +- .../StaticLocationViewingCoordinator.swift | 14 +-- ...MockStaticLocationViewingScreenState.swift | 11 +- .../StaticLocationViewingModels.swift | 7 +- .../StaticLocationViewingViewModel.swift | 9 +- ...aticLocationViewingViewModelProtocol.swift | 3 +- .../UI/StaticLocationViewingUITests.swift | 2 +- .../StaticLocationViewingViewModelTests.swift | 9 +- .../View/StaticLocationView.swift | 3 +- .../OnboardingAvatarCoordinator.swift | 10 +- .../MockOnboardingAvatarScreenState.swift | 4 +- .../Avatar/OnboardingAvatarModels.swift | 2 +- .../Avatar/OnboardingAvatarViewModel.swift | 7 +- .../OnboardingAvatarViewModelProtocol.swift | 3 +- .../Test/UI/OnboardingAvatarUITests.swift | 2 +- .../Unit/OnboardingAvatarViewModelTests.swift | 4 +- .../Avatar/View/OnboardingAvatarScreen.swift | 6 +- .../OnboardingCelebrationCoordinator.swift | 4 +- ...MockOnboardingCelebrationScreenState.swift | 4 +- .../OnboardingCelebrationModels.swift | 2 +- .../OnboardingCelebrationViewModel.swift | 5 +- ...boardingCelebrationViewModelProtocol.swift | 3 +- .../UI/OnboardingCelebrationUITests.swift | 2 +- .../OnboardingCelebrationViewModelTests.swift | 2 +- .../View/OnboardingCelebrationScreen.swift | 5 +- .../OnboardingBreakerScreenBackground.swift | 16 ++- .../Common/OnboardingButtonStyle.swift | 2 +- .../Onboarding/Common/OnboardingIcon.swift | 3 +- .../Onboarding/Common/OnboardingMetrics.swift | 2 +- .../Common/OnboardingTintedFullStopText.swift | 4 +- ...OnboardingCongratulationsCoordinator.swift | 4 +- ...OnboardingCongratulationsScreenState.swift | 4 +- .../OnboardingCongratulationsModels.swift | 2 +- .../OnboardingCongratulationsViewModel.swift | 5 +- ...dingCongratulationsViewModelProtocol.swift | 3 +- .../UI/OnboardingCongratulationsUITests.swift | 2 +- ...oardingCongratulationsViewModelTests.swift | 2 +- .../OnboardingCongratulationsScreen.swift | 5 +- .../OnboardingDisplayNameCoordinator.swift | 6 +- ...MockOnboardingDisplayNameScreenState.swift | 4 +- .../OnboardingDisplayNameModels.swift | 2 +- .../OnboardingDisplayNameViewModel.swift | 7 +- ...boardingDisplayNameViewModelProtocol.swift | 3 +- .../UI/OnboardingDisplayNameUITests.swift | 6 +- .../OnboardingDisplayNameViewModelTests.swift | 4 +- .../View/OnboardingDisplayNameScreen.swift | 4 +- .../OnboardingSplashScreenCoordinator.swift | 6 +- ...ockOnboardingSplashScreenScreenState.swift | 4 +- .../OnboardingSplashScreenModels.swift | 9 +- .../OnboardingSplashScreenViewModel.swift | 9 +- ...OnboardingSplashScreenViewModelTests.swift | 8 +- .../View/OnboardingSplashScreen.swift | 13 +-- .../View/OnboardingSplashScreenPage.swift | 11 +- .../OnboardingSplashScreenPageIndicator.swift | 7 +- ...nboardingUseCaseSelectionCoordinator.swift | 6 +- .../MockOnboardingUseCaseScreenState.swift | 4 +- .../UseCase/OnboardingUseCaseModels.swift | 2 +- .../UseCase/OnboardingUseCaseViewModel.swift | 5 +- .../OnboardingUseCaseViewModelProtocol.swift | 3 +- .../Test/UI/OnboardingUseCaseUITests.swift | 2 +- .../OnboardingUseCaseViewModelTests.swift | 2 +- .../View/OnboardingUseCaseButton.swift | 3 +- .../OnboardingUseCaseSelectionScreen.swift | 3 +- .../AllChatsOnboardingModels.swift | 2 +- .../AllChatsOnboardingViewModel.swift | 11 +- .../AllChatsOnboardingViewModelProtocol.swift | 3 +- .../AllChatsOnboardingCoordinator.swift | 11 +- ...OnboardingCoordinatorBridgePresenter.swift | 4 +- .../View/AllChatsOnboarding.swift | 9 +- .../View/AllChatsOnboardingPage.swift | 5 +- ...mNotificationSettingsBridgePresenter.swift | 16 ++- .../RoomNotificationSettingsCoordinator.swift | 23 ++-- .../RoomNotificationSettingsViewState.swift | 3 +- ...oomNotificationSettingsViewStateType.swift | 1 - .../Model/RoomNotificationState.swift | 4 +- .../MXRoomNotificationSettingsService.swift | 60 +++++----- .../MockRoomNotificationSettingsService.swift | 5 +- .../RoomNotificationSettingsServiceType.swift | 3 +- .../View/FormItemButtonStyle.swift | 2 +- .../View/FormPickerItem.swift | 2 - .../View/FormSectionFooter.swift | 3 +- .../View/FormSectionHeader.swift | 3 +- .../View/RoomNotificationSettings.swift | 4 +- .../View/RoomNotificationSettingsHeader.swift | 3 +- .../View/VectorForm.swift | 5 +- ...NotificationSettingsSwiftUIViewModel.swift | 7 +- .../RoomNotificationSettingsViewModel.swift | 29 ++--- ...oomNotificationSettingsViewModelType.swift | 3 +- .../Coordinator/PollEditFormCoordinator.swift | 27 +++-- .../PollEditForm/PollEditFormModels.swift | 6 +- .../PollEditFormScreenState.swift | 4 +- .../PollEditForm/PollEditFormViewModel.swift | 23 ++-- .../PollEditFormViewModelProtocol.swift | 2 +- .../Test/UI/PollEditFormUITests.swift | 2 +- .../Unit/PollEditFormViewModelTests.swift | 4 +- .../Room/PollEditForm/View/PollEditForm.swift | 4 +- .../View/PollEditFormAnswerOptionView.swift | 13 +-- .../View/PollEditFormTypePicker.swift | 3 +- .../Coordinator/RoomAccessCoordinator.swift | 32 ++--- ...RoomAccessCoordinatorBridgePresenter.swift | 13 +-- .../RoomAccessCoordinatorParameters.swift | 1 - .../RoomAccessTypeChooserCoordinator.swift | 5 +- ...MockRoomAccessTypeChooserScreenState.swift | 5 +- .../RoomAccessTypeChooserModels.swift | 2 +- .../RoomAccessTypeChooserViewModel.swift | 14 +-- ...omAccessTypeChooserViewModelProtocol.swift | 2 +- .../RoomAccessTypeChooserService.swift | 29 ++--- .../MockRoomAccessTypeChooserService.swift | 21 ++-- ...RoomAccessTypeChooserServiceProtocol.swift | 4 +- .../UI/RoomAccessTypeChooserUITests.swift | 4 +- .../RoomAccessTypeChooserViewModelTests.swift | 8 +- .../View/RoomAccessTypeChooser.swift | 6 +- .../View/RoomAccessTypeChooserRow.swift | 1 - ...rictedAccessSpaceChooserViewProvider.swift | 5 +- ...ctedAccessSpaceChooserItemsProcessor.swift | 9 +- ...RestrictedAccessSpaceChooserSelector.swift | 4 +- .../RoomSuggestionCoordinator.swift | 24 ++-- ...SuggestionCoordinatorBridgePresenter.swift | 7 +- .../RoomSuggestionCoordinatorParameters.swift | 1 - ...omSuggestionSpaceChooserViewProvider.swift | 5 +- ...SuggestionSpaceChooserItemsProcessor.swift | 20 ++-- .../RoomSuggestionSpaceChooserSelector.swift | 4 +- .../Coordinator/RoomUpgradeCoordinator.swift | 6 +- .../MockRoomUpgradeScreenState.swift | 4 +- .../Room/RoomUpgrade/RoomUpgradeModels.swift | 2 +- .../RoomUpgrade/RoomUpgradeViewModel.swift | 15 ++- .../RoomUpgradeViewModelProtocol.swift | 3 +- .../MatrixSDK/RoomUpgradeService.swift | 33 +++--- .../Service/Mock/MockRoomUpgradeService.swift | 16 ++- .../Service/RoomUpgradeServiceProtocol.swift | 4 +- .../Test/UI/RoomUpgradeUITests.swift | 2 +- .../Test/Unit/RoomUpgradeViewModelTests.swift | 8 +- .../Room/RoomUpgrade/View/RoomUpgrade.swift | 9 +- .../Coordinator/TimelinePollCoordinator.swift | 52 ++++----- .../Coordinator/TimelinePollProvider.swift | 8 +- .../Test/UI/TimelinePollUITests.swift | 2 +- .../Unit/TimelinePollViewModelTests.swift | 4 +- .../TimelinePoll/TimelinePollModels.swift | 8 +- .../TimelinePollScreenState.swift | 8 +- .../TimelinePoll/TimelinePollViewModel.swift | 14 +-- .../TimelinePollViewModelProtocol.swift | 2 +- .../View/TimelinePollAnswerOptionButton.swift | 34 +++--- .../TimelinePoll/View/TimelinePollView.swift | 12 +- .../UserSuggestionCoordinator.swift | 17 ++- .../UserSuggestionCoordinatorBridge.swift | 13 +-- .../Service/UserSuggestionService.swift | 17 ++- .../UserSuggestionServiceProtocol.swift | 6 +- .../Test/UI/UserSuggestionUITests.swift | 4 +- .../Unit/UserSuggestionServiceTests.swift | 10 +- .../UserSuggestion/UserSuggestionModels.swift | 2 +- .../UserSuggestionScreenState.swift | 10 +- .../UserSuggestionViewModel.swift | 17 ++- .../UserSuggestionViewModelProtocol.swift | 2 +- .../View/UserSuggestionList.swift | 7 +- .../View/UserSuggestionListItem.swift | 5 +- .../View/UserSuggestionListWithInput.swift | 9 +- .../ChangePassword/ChangePasswordModels.swift | 6 +- .../ChangePasswordViewModel.swift | 5 +- .../ChangePasswordViewModelProtocol.swift | 3 +- .../ChangePasswordBridgePresenter.swift | 8 +- .../ChangePasswordCoordinator.swift | 5 +- .../MockChangePasswordScreenState.swift | 4 +- .../Test/UI/ChangePasswordUITests.swift | 3 +- .../Unit/ChangePasswordViewModelTests.swift | 4 +- .../View/ChangePasswordScreen.swift | 9 +- .../NotificationSettingsBridgePresenter.swift | 13 +-- .../NotificationSettingsCoordinator.swift | 14 +-- .../MatrixSDK/MXNotificationPushRule.swift | 11 +- .../Model/Mock/MockNotificationPushRule.swift | 4 +- .../Model/NotificationActions.swift | 2 +- .../Model/NotificationIndex.swift | 6 +- .../NotificationPushRuleDefinitions.swift | 3 +- .../Model/NotificationPushRuleIds.swift | 3 +- .../Model/NotificationPushRuleType.swift | 2 +- .../Model/NotificationSettingsScreen.swift | 4 +- .../Model/NotificationStandardActions.swift | 2 +- .../MXNotificationSettingsService.swift | 13 +-- .../MockNotificationSettingsService.swift | 8 +- .../NotificationSettingsServiceType.swift | 4 +- .../Settings/Notifications/View/Chip.swift | 5 +- .../Settings/Notifications/View/Chips.swift | 8 +- .../Notifications/View/ChipsInput.swift | 9 +- .../View/DefaultNotificationSettings.swift | 4 +- .../View/FormInputFieldStyle.swift | 7 +- ...ntionsAndKeywordNotificationSettings.swift | 4 +- .../View/NotificationSettings.swift | 3 +- .../View/NotificationSettingsKeywords.swift | 3 +- .../View/OtherNotificationSettings.swift | 2 +- .../NotificationSettingsViewModel.swift | 23 ++-- .../AddRoomSelectorViewProvider.swift | 4 +- .../MatrixSDK/AddRoomItemsProcessor.swift | 9 +- .../View/AddRoomSelector.swift | 5 +- .../Coordinator/LeaveSpaceViewProvider.swift | 5 +- .../MatrixSDK/LeaveSpaceItemsProcessor.swift | 19 ++- .../Spaces/LeaveSpace/View/LeaveSpace.swift | 4 +- .../MatrixItemChooserCoordinator.swift | 5 +- .../MatrixItemChooserModels.swift | 6 +- .../MatrixItemChooserViewModel.swift | 20 ++-- .../MatrixItemChooserViewModelProtocol.swift | 3 +- .../MockMatrixItemChooserScreenState.swift | 2 +- .../MatrixItemChooserServiceProtocol.swift | 4 +- ...xItemChooserDirectChildrenDataSource.swift | 7 +- ...ixItemChooserRoomAncestorsDataSource.swift | 4 +- ...emChooserRoomDirectParentsDataSource.swift | 3 +- ...omRestrictedAllowedParentsDataSource.swift | 13 ++- .../MatrixItemChooserRoomsDataSource.swift | 2 +- .../MatrixSDK/MatrixItemChooserService.swift | 26 +++-- .../MatrixItemChooserUsersDataSource.swift | 2 +- .../MatrixSDK/MatrixListItemData+Riot.swift | 4 +- .../Mock/MockMatrixItemChooserService.swift | 16 ++- .../Test/UI/MatrixItemChooserUITests.swift | 3 +- .../MatrixItemChooserViewModelTests.swift | 4 +- .../View/MatrixItemChooser.swift | 8 +- .../View/MatrixItemChooserListRow.swift | 3 +- .../View/MatrixItemChooserSectionHeader.swift | 3 +- .../RoomAncestorSelectorViewProvider.swift | 5 +- .../RoomAncestorSelectorItemsProcessor.swift | 9 +- .../View/RoomAncestorSelector.swift | 4 +- .../SpaceCreationCoordinator.swift | 35 +++--- .../SpaceCreationCoordinatorAction.swift | 2 +- .../SpaceCreationCoordinatorParameters.swift | 3 +- ...SpaceCreationEmailInvitesCoordinator.swift | 15 ++- ...ionEmailInvitesCoordinatorParameters.swift | 2 +- ...reationEmailInvitesCoordinatorAction.swift | 2 +- ...SpaceCreationEmailInvitesStateAction.swift | 2 +- .../SpaceCreationEmailInvitesViewAction.swift | 2 +- ...reationEmailInvitesViewModelBindings.swift | 2 +- ...eCreationEmailInvitesViewModelResult.swift | 2 +- .../SpaceCreationEmailInvitesViewState.swift | 2 +- .../SpaceCreationEmailInvitesService.swift | 9 +- ...SpaceCreationEmailInvitesScreenState.swift | 4 +- ...MockSpaceCreationEmailInvitesService.swift | 10 +- ...eCreationEmailInvitesServiceProtocol.swift | 4 +- .../UI/SpaceCreationEmailInvitesUITests.swift | 2 +- ...ceCreationEmailInvitesViewModelTests.swift | 4 +- .../View/SpaceCreationEmailInvites.swift | 5 +- .../SpaceCreationEmailInvitesViewModel.swift | 17 ++- ...reationEmailInvitesViewModelProtocol.swift | 3 +- ...reationMatrixItemChooserViewProvider.swift | 2 +- .../SpaceCreationAddRoomsItemsProcessor.swift | 6 +- ...aceCreationInviteUsersItemsProcessor.swift | 6 +- .../View/SpaceCreationMatrixItemChooser.swift | 2 - .../SpaceCreationMenuCoordinator.swift | 6 +- ...aceCreationMenuCoordinatorParamaters.swift | 2 +- .../SpaceCreationMenuCoordinatorAction.swift | 2 +- .../Model/SpaceCreationMenuRoom.swift | 4 +- .../Model/SpaceCreationMenuStateAction.swift | 5 +- .../Model/SpaceCreationMenuViewAction.swift | 2 +- .../SpaceCreationMenuViewModelAction.swift | 2 +- .../Model/SpaceCreationMenuViewState.swift | 2 +- .../Model/SpaceCreationParameters.swift | 24 ++-- .../Test/UI/SpaceCreationMenuUITests.swift | 6 +- .../SpaceCreationMenuViewModelTests.swift | 7 +- .../View/SpaceCreationMenu.swift | 4 +- .../SpaceCreationMenuViewModel.swift | 19 ++- .../SpaceCreationMenuViewModelProtocol.swift | 2 +- .../SpaceCreationPostProcessCoordinator.swift | 6 +- ...tionPostProcessCoordinatorParameters.swift | 2 +- ...CreationPostProcessCoordinatorAction.swift | 2 +- .../SpaceCreationPostProcessStateAction.swift | 2 +- .../Model/SpaceCreationPostProcessTask.swift | 7 +- .../SpaceCreationPostProcessViewAction.swift | 2 +- ...ceCreationPostProcessViewModelResult.swift | 2 +- .../SpaceCreationPostProcessViewState.swift | 2 +- .../SpaceCreationPostProcessService.swift | 78 ++++++------- ...kSpaceCreationPostProcessScreenState.swift | 5 +- .../MockSpaceCreationPostProcessService.swift | 17 ++- ...ceCreationPostProcessServiceProtocol.swift | 4 +- .../UI/SpaceCreationPostProcessUITests.swift | 2 +- ...aceCreationPostProcessViewModelTests.swift | 5 +- .../View/SpaceCreationPostProcess.swift | 10 +- .../View/SpaceCreationPostProcessItem.swift | 2 +- .../SpaceCreationPostProcessViewModel.swift | 24 ++-- ...CreationPostProcessViewModelProtocol.swift | 3 +- .../SpaceCreationRoomsCoordinator.swift | 5 +- ...ceCreationRoomsCoordinatorParameters.swift | 2 +- .../SpaceCreationRoomsCoordinatorAction.swift | 2 +- .../Model/SpaceCreationRoomsPresence.swift | 2 +- .../Model/SpaceCreationRoomsStateAction.swift | 5 +- .../Model/SpaceCreationRoomsViewAction.swift | 2 +- .../SpaceCreationRoomsViewModelBindings.swift | 2 +- .../SpaceCreationRoomsViewModelResult.swift | 2 +- .../Model/SpaceCreationRoomsViewState.swift | 2 +- .../MockSpaceCreationRoomsScreenState.swift | 4 +- .../Test/UI/SpaceCreationRoomsUITests.swift | 2 +- .../SpaceCreationRoomsViewModelTests.swift | 4 +- .../View/SpaceCreationRooms.swift | 9 +- .../SpaceCreationRoomsViewModel.swift | 15 +-- .../SpaceCreationRoomsViewModelProtocol.swift | 3 +- .../SpaceCreationSettingsCoordinator.swift | 7 +- ...reationSettingsCoordinatorParamaters.swift | 2 +- ...ationSettingsAddressValidationStatus.swift | 2 +- ...aceCreationSettingsCoordinatorAction.swift | 2 +- .../SpaceCreationSettingsStateAction.swift | 2 +- .../SpaceCreationSettingsViewAction.swift | 2 +- ...SpaceCreationSettingsViewModelAction.swift | 2 +- ...aceCreationSettingsViewModelBindings.swift | 2 +- .../SpaceCreationSettingsViewState.swift | 3 +- .../SpaceCreationSettingsService.swift | 28 ++--- ...MockSpaceCreationSettingsScreenState.swift | 7 +- .../MockSpaceCreationSettingsService.swift | 10 +- ...SpaceCreationSettingsServiceProtocol.swift | 4 +- .../UI/SpaceCreationSettingsUITests.swift | 4 +- .../SpaceCreationSettingsViewModelTests.swift | 6 +- .../View/SpaceCreationSettings.swift | 25 ++-- .../SpaceCreationSettingsViewModel.swift | 13 ++- ...aceCreationSettingsViewModelProtocol.swift | 2 +- .../SpaceSelectorBottomSheetCoordinator.swift | 26 ++--- ...ottomSheetCoordinatorBridgePresenter.swift | 6 +- .../SpaceSelectorCoordinator.swift | 5 +- .../MockSpaceSelectorScreenState.swift | 4 +- .../MatrixSDK/SpaceSelectorService.swift | 17 ++- .../Mock/MockSpaceSelectorService.swift | 9 +- .../SpaceSelectorServiceProtocol.swift | 4 +- .../SpaceSelector/SpaceSelectorModels.swift | 4 +- .../SpaceSelectorViewModel.swift | 11 +- .../SpaceSelectorViewModelProtocol.swift | 3 +- .../Test/UI/SpaceSelectorUITests.swift | 6 +- .../Unit/SpaceSelectorViewModelTests.swift | 6 +- .../SpaceSelector/View/SpaceSelector.swift | 12 +- .../View/SpaceSelectorListRow.swift | 8 +- .../SpaceSettingsModalCoordinator.swift | 38 +++--- ...tingsModalCoordinatorBridgePresenter.swift | 7 +- ...ceSettingsModalCoordinatorParameters.swift | 1 - .../SpaceSettingsCoordinator.swift | 6 +- .../MockSpaceSettingsScreenState.swift | 10 +- .../MatrixSDK/SpaceSettingsService.swift | 110 +++++++++--------- .../Mock/MockSpaceSettingsService.swift | 24 ++-- .../SpaceSettingsServiceProtocol.swift | 5 +- .../SpaceSettings/SpaceSettingsModels.swift | 6 +- .../SpaceSettingsViewModel.swift | 17 +-- .../SpaceSettingsViewModelProtocol.swift | 3 +- .../Test/UI/SpaceSettingsUITests.swift | 2 +- .../Unit/SpaceSettingsViewModelTests.swift | 7 +- .../SpaceSettings/View/SpaceSettings.swift | 66 ++++++----- .../View/SpaceSettingsOptionListItem.swift | 5 +- .../TemplateSimpleScreenCoordinator.swift | 5 +- .../MockTemplateSimpleScreenScreenState.swift | 4 +- .../TemplateSimpleScreenModels.swift | 2 +- .../TemplateSimpleScreenViewModel.swift | 5 +- ...emplateSimpleScreenViewModelProtocol.swift | 3 +- .../Test/UI/TemplateSimpleScreenUITests.swift | 2 +- .../TemplateSimpleScreenViewModelTests.swift | 2 +- .../View/TemplateSimpleScreen.swift | 7 +- .../TemplateUserProfileCoordinator.swift | 5 +- .../MockTemplateUserProfileScreenState.swift | 8 +- .../TemplateUserProfileService.swift | 12 +- .../Mock/MockTemplateUserProfileService.swift | 18 ++- .../TemplateUserProfileServiceProtocol.swift | 5 +- .../TemplateUserProfileModels.swift | 2 +- .../TemplateUserProfileViewModel.swift | 11 +- ...TemplateUserProfileViewModelProtocol.swift | 3 +- .../Test/UI/TemplateUserProfileUITests.swift | 3 +- .../TemplateUserProfileViewModelTests.swift | 5 +- .../View/TemplateUserProfile.swift | 5 +- .../View/TemplateUserProfileHeader.swift | 9 +- .../TemplateUserProfilePresenceView.swift | 6 +- .../TemplateRoomsCoordinator.swift | 24 ++-- .../TemplateRoomsCoordinatorParameters.swift | 1 - .../TemplateRoomChatCoordinator.swift | 5 +- .../MockTemplateRoomChatScreenState.swift | 4 +- .../MatrixSDK/TemplateRoomChatService.swift | 41 ++++--- .../Mock/MockTemplateRoomChatService.swift | 19 ++- .../TemplateRoomChatServiceProtocol.swift | 4 +- .../TemplateRoomChatModels.swift | 3 +- .../TemplateRoomChatViewModel.swift | 20 ++-- .../TemplateRoomChatViewModelProtocol.swift | 2 +- .../Test/UI/TemplateRoomChatUITests.swift | 9 +- .../Unit/TemplateRoomChatViewModelTests.swift | 10 +- .../View/TemplateRoomChat.swift | 12 +- .../TemplateRoomChatBubbleContentView.swift | 1 - .../View/TemplateRoomChatBubbleImage.swift | 1 - .../View/TemplateRoomChatBubbleMessage.swift | 1 - .../View/TemplateRoomChatBubbleView.swift | 7 +- .../TemplateRoomListCoordinator.swift | 4 +- .../MockTemplateRoomListScreenState.swift | 3 +- .../MatrixSDK/TemplateRoomListService.swift | 14 +-- .../Mock/MockTemplateRoomListService.swift | 7 +- .../TemplateRoomListServiceProtocol.swift | 4 +- .../TemplateRoomListModels.swift | 2 +- .../TemplateRoomListViewModel.swift | 11 +- .../TemplateRoomListViewModelProtocol.swift | 2 +- .../Test/UI/TemplateRoomListUITests.swift | 7 +- .../Unit/TemplateRoomListViewModelTests.swift | 10 +- .../View/TemplateRoomList.swift | 6 +- .../View/TemplateRoomListRow.swift | 7 +- .../UserSessions/Common/DeviceType.swift | 2 +- .../UserSessions/Common/UserSessionInfo.swift | 5 +- .../UserSessionLastActivityFormatter.swift | 1 - .../Common/UserSessionNameFormatter.swift | 4 +- .../Common/View/DeviceAvatarView.swift | 8 +- .../Common/View/DeviceAvatarViewData.swift | 2 +- .../Common/View/SeparatorLine.swift | 2 +- .../Common/View/UserSessionCardView.swift | 22 ++-- .../Common/View/UserSessionCardViewData.swift | 4 +- .../UserSessionsFlowCoordinator.swift | 4 +- .../UserSessionDetailsCoordinator.swift | 4 +- .../MockUserSessionDetailsScreenState.swift | 6 +- .../Test/UI/UserSessionDetailsUITests.swift | 4 +- .../UserSessionDetailsViewModelTests.swift | 3 +- .../UserSessionDetailsModels.swift | 14 +-- .../UserSessionDetailsViewModel.swift | 4 +- .../UserSessionDetailsViewModelProtocol.swift | 2 +- .../View/UserSessionDetails.swift | 2 +- .../View/UserSessionDetailsItem.swift | 12 +- .../UserSessionOverviewCoordinator.swift | 6 +- .../MockUserSessionOverviewScreenState.swift | 26 ++--- .../Test/UI/UserSessionOverviewUITests.swift | 2 +- .../UserSessionOverviewViewModelTests.swift | 14 +-- .../UserSessionOverviewModels.swift | 2 +- .../UserSessionOverviewViewModel.swift | 4 +- ...UserSessionOverviewViewModelProtocol.swift | 2 +- .../View/UserSessionOverview.swift | 14 +-- .../UserSessionOverviewDisclosureCell.swift | 6 +- .../UserSessionsOverviewCoordinator.swift | 12 +- .../MockUserSessionsOverviewScreenState.swift | 8 +- .../UserSessionsOverviewService.swift | 10 +- .../MockUserSessionsOverviewService.swift | 14 +-- .../UserSessionsOverviewServiceProtocol.swift | 2 +- .../Test/UI/UserSessionsOverviewUITests.swift | 2 +- .../UserSessionsOverviewViewModelTests.swift | 4 +- .../UserSessionsOverviewModels.swift | 4 +- .../UserSessionsOverviewViewModel.swift | 6 +- ...serSessionsOverviewViewModelProtocol.swift | 2 +- .../View/UserSessionListItem.swift | 4 +- .../View/UserSessionListItemViewData.swift | 3 +- .../View/UserSessionsOverview.swift | 5 +- RiotSwiftUI/RiotSwiftUIApp.swift | 4 +- RiotSwiftUI/target.yml | 12 ++ 663 files changed, 2329 insertions(+), 2840 deletions(-) create mode 100644 .swiftformat diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 000000000..024e43468 --- /dev/null +++ b/.swiftformat @@ -0,0 +1,15 @@ +--swiftversion 5.6 + +--exclude CommonKit, Config, DesignKit, Riot, RiotNSE, RiotShareExtension, RiotTests, SiriIntents, Tools, Variants, vendor + +--disable wrapMultiLineStatementBraces +--disable hoistPatternLet + +--commas inline +--ifdef no-indent +--nospaceoperators ...,..< +--stripunusedargs closure-only +--trimwhitespace nonblank-lines +--wrapparameters after-first +--redundanttype inferred +--emptybraces spaced \ No newline at end of file diff --git a/RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptModels.swift b/RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptModels.swift index 6e4e34f30..39f339e87 100644 --- a/RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptModels.swift +++ b/RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptViewModel.swift b/RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptViewModel.swift index 3fa94d351..a0ce70a99 100644 --- a/RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptViewModel.swift +++ b/RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptViewModel.swift @@ -14,14 +14,13 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias AnalyticsPromptViewModelType = StateStoreViewModel + Never, + AnalyticsPromptViewAction> class AnalyticsPromptViewModel: AnalyticsPromptViewModelType { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptCoordinator.swift b/RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptCoordinator.swift index c41749369..fb79b8df8 100644 --- a/RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptCoordinator.swift +++ b/RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptCoordinator.swift @@ -22,17 +22,16 @@ struct AnalyticsPromptCoordinatorParameters { } final class AnalyticsPromptCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private private let parameters: AnalyticsPromptCoordinatorParameters private let analyticsPromptHostingController: UIViewController - private var _analyticsPromptViewModel: Any? = nil + private var _analyticsPromptViewModel: Any? fileprivate var analyticsPromptViewModel: AnalyticsPromptViewModel { - return _analyticsPromptViewModel as! AnalyticsPromptViewModel + _analyticsPromptViewModel as! AnalyticsPromptViewModel } // MARK: Public @@ -84,6 +83,6 @@ final class AnalyticsPromptCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.analyticsPromptHostingController + analyticsPromptHostingController } } diff --git a/RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptStrings.swift b/RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptStrings.swift index 9ada08435..0d5d844c8 100644 --- a/RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptStrings.swift +++ b/RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptStrings.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,4 +25,3 @@ struct AnalyticsPromptStrings: AnalyticsPromptStringsProtocol { withAllowedTags: ["b", "p"], font: UIFont.systemFont(ofSize: UIFont.systemFontSize)) } - diff --git a/RiotSwiftUI/Modules/AnalyticsPrompt/MockAnalyticsPromptScreenState.swift b/RiotSwiftUI/Modules/AnalyticsPrompt/MockAnalyticsPromptScreenState.swift index 290a88836..7d222aa31 100644 --- a/RiotSwiftUI/Modules/AnalyticsPrompt/MockAnalyticsPromptScreenState.swift +++ b/RiotSwiftUI/Modules/AnalyticsPrompt/MockAnalyticsPromptScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/AnalyticsPrompt/MockAnalyticsPromptStrings.swift b/RiotSwiftUI/Modules/AnalyticsPrompt/MockAnalyticsPromptStrings.swift index 37a38e32d..2fd7c0018 100644 --- a/RiotSwiftUI/Modules/AnalyticsPrompt/MockAnalyticsPromptStrings.swift +++ b/RiotSwiftUI/Modules/AnalyticsPrompt/MockAnalyticsPromptStrings.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/AnalyticsPrompt/Test/UI/AnalyticsPromptUITests.swift b/RiotSwiftUI/Modules/AnalyticsPrompt/Test/UI/AnalyticsPromptUITests.swift index 2013f0866..46eed139c 100644 --- a/RiotSwiftUI/Modules/AnalyticsPrompt/Test/UI/AnalyticsPromptUITests.swift +++ b/RiotSwiftUI/Modules/AnalyticsPrompt/Test/UI/AnalyticsPromptUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class AnalyticsPromptUITests: MockScreenTestCase { /// Verify that the prompt is displayed correctly for new users. diff --git a/RiotSwiftUI/Modules/AnalyticsPrompt/View/AnalyticsPrompt.swift b/RiotSwiftUI/Modules/AnalyticsPrompt/View/AnalyticsPrompt.swift index 16cbeb989..a400af7aa 100644 --- a/RiotSwiftUI/Modules/AnalyticsPrompt/View/AnalyticsPrompt.swift +++ b/RiotSwiftUI/Modules/AnalyticsPrompt/View/AnalyticsPrompt.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import SwiftUI /// A prompt that asks the user whether they would like to enable Analytics or not. struct AnalyticsPrompt: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/AnalyticsPrompt/View/AnalyticsPromptCheckmarkItem.swift b/RiotSwiftUI/Modules/AnalyticsPrompt/View/AnalyticsPromptCheckmarkItem.swift index cec25f2d7..1ec120069 100644 --- a/RiotSwiftUI/Modules/AnalyticsPrompt/View/AnalyticsPromptCheckmarkItem.swift +++ b/RiotSwiftUI/Modules/AnalyticsPrompt/View/AnalyticsPromptCheckmarkItem.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct AnalyticsPromptCheckmarkItem: View { - // MARK: - Properties // MARK: Private @@ -40,7 +39,7 @@ struct AnalyticsPromptCheckmarkItem: View { let range = NSRange(location: 0, length: attributedString.length) let string = attributedString.string as NSString - attributedString.enumerateAttributes(in: range, options: []) { attributes, range, stop in + attributedString.enumerateAttributes(in: range, options: []) { attributes, range, _ in var isBold = false if let font = attributes[.font] as? UIFont { @@ -54,7 +53,7 @@ struct AnalyticsPromptCheckmarkItem: View { } init(string: String) { - self.components = [StringComponent(string: string, isBold: false)] + components = [StringComponent(string: string, isBold: false)] } // MARK: - Views @@ -75,11 +74,10 @@ struct AnalyticsPromptCheckmarkItem: View { // MARK: - Previews struct AnalyticsPromptCheckmarkItem_Previews: PreviewProvider { - static let strings = MockAnalyticsPromptStrings() static var previews: some View { - VStack(alignment:.leading) { + VStack(alignment: .leading) { AnalyticsPromptCheckmarkItem(attributedString: strings.point1) AnalyticsPromptCheckmarkItem(attributedString: strings.point2) AnalyticsPromptCheckmarkItem(attributedString: strings.longString) diff --git a/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordModels.swift b/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordModels.swift index 6c8230b18..838767317 100644 --- a/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordModels.swift +++ b/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordViewModel.swift b/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordViewModel.swift index 89f418a07..fb6fc88e2 100644 --- a/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordViewModel.swift @@ -17,10 +17,9 @@ import SwiftUI typealias AuthenticationChoosePasswordViewModelType = StateStoreViewModel + Never, + AuthenticationChoosePasswordViewAction> class AuthenticationChoosePasswordViewModel: AuthenticationChoosePasswordViewModelType, AuthenticationChoosePasswordViewModelProtocol { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordViewModelProtocol.swift b/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordViewModelProtocol.swift index 47c57304e..46a148ec4 100644 --- a/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol AuthenticationChoosePasswordViewModelProtocol { - var callback: (@MainActor (AuthenticationChoosePasswordViewModelResult) -> Void)? { get set } var context: AuthenticationChoosePasswordViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Authentication/ChoosePassword/Coordinator/AuthenticationChoosePasswordCoordinator.swift b/RiotSwiftUI/Modules/Authentication/ChoosePassword/Coordinator/AuthenticationChoosePasswordCoordinator.swift index 9d02e4000..df3f7d793 100644 --- a/RiotSwiftUI/Modules/Authentication/ChoosePassword/Coordinator/AuthenticationChoosePasswordCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/ChoosePassword/Coordinator/AuthenticationChoosePasswordCoordinator.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI struct AuthenticationChoosePasswordCoordinatorParameters { let loginWizard: LoginWizard @@ -29,7 +29,6 @@ enum AuthenticationChoosePasswordCoordinatorResult { } final class AuthenticationChoosePasswordCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -79,7 +78,7 @@ final class AuthenticationChoosePasswordCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.authenticationChoosePasswordHostingController + authenticationChoosePasswordHostingController } // MARK: - Private diff --git a/RiotSwiftUI/Modules/Authentication/ChoosePassword/MockAuthenticationChoosePasswordScreenState.swift b/RiotSwiftUI/Modules/Authentication/ChoosePassword/MockAuthenticationChoosePasswordScreenState.swift index c18b1398a..afe021fbc 100644 --- a/RiotSwiftUI/Modules/Authentication/ChoosePassword/MockAuthenticationChoosePasswordScreenState.swift +++ b/RiotSwiftUI/Modules/Authentication/ChoosePassword/MockAuthenticationChoosePasswordScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,7 +34,7 @@ enum MockAuthenticationChoosePasswordScreenState: MockScreenState, CaseIterable } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel: AuthenticationChoosePasswordViewModel switch self { case .emptyPassword: diff --git a/RiotSwiftUI/Modules/Authentication/ChoosePassword/Test/UI/AuthenticationChoosePasswordUITests.swift b/RiotSwiftUI/Modules/Authentication/ChoosePassword/Test/UI/AuthenticationChoosePasswordUITests.swift index d1fffa0fb..2d6539e1f 100644 --- a/RiotSwiftUI/Modules/Authentication/ChoosePassword/Test/UI/AuthenticationChoosePasswordUITests.swift +++ b/RiotSwiftUI/Modules/Authentication/ChoosePassword/Test/UI/AuthenticationChoosePasswordUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class AuthenticationChoosePasswordUITests: MockScreenTestCase { func testEmptyPassword() { @@ -93,7 +93,6 @@ class AuthenticationChoosePasswordUITests: MockScreenTestCase { XCTAssertTrue(signoutAllDevicesToggle.exists, "Sign out all devices toggle should exist") XCTAssertTrue(signoutAllDevicesToggle.isOn, "Sign out all devices should be checked") } - } extension XCUIElement { diff --git a/RiotSwiftUI/Modules/Authentication/ChoosePassword/Test/Unit/AuthenticationChoosePasswordViewModelTests.swift b/RiotSwiftUI/Modules/Authentication/ChoosePassword/Test/Unit/AuthenticationChoosePasswordViewModelTests.swift index ac9219ea1..34d236e09 100644 --- a/RiotSwiftUI/Modules/Authentication/ChoosePassword/Test/Unit/AuthenticationChoosePasswordViewModelTests.swift +++ b/RiotSwiftUI/Modules/Authentication/ChoosePassword/Test/Unit/AuthenticationChoosePasswordViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import XCTest @testable import RiotSwiftUI class AuthenticationChoosePasswordViewModelTests: XCTestCase { - @MainActor func testInitialState() async { let viewModel = AuthenticationChoosePasswordViewModel() let context = viewModel.context @@ -29,5 +28,4 @@ class AuthenticationChoosePasswordViewModelTests: XCTestCase { XCTAssert(context.viewState.hasInvalidPassword, "The view model should start with an invalid password.") XCTAssertFalse(context.signoutAllDevices, "The view model should start with sign out of all devices unchecked.") } - } diff --git a/RiotSwiftUI/Modules/Authentication/ChoosePassword/View/AuthenticationChoosePasswordScreen.swift b/RiotSwiftUI/Modules/Authentication/ChoosePassword/View/AuthenticationChoosePasswordScreen.swift index ff76409d6..704ca6a36 100644 --- a/RiotSwiftUI/Modules/Authentication/ChoosePassword/View/AuthenticationChoosePasswordScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/ChoosePassword/View/AuthenticationChoosePasswordScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct AuthenticationChoosePasswordScreen: View { - // MARK: - Properties // MARK: Private @@ -100,7 +99,7 @@ struct AuthenticationChoosePasswordScreen: View { configuration: UIKitTextInputConfiguration(returnKeyType: .done, isSecureTextEntry: true), onCommit: submit) - .accessibilityIdentifier("passwordTextField") + .accessibilityIdentifier("passwordTextField") } /// Sends the `send` view action so long as a valid email address has been input. diff --git a/RiotSwiftUI/Modules/Authentication/Common/AuthenticationHomeserverViewData.swift b/RiotSwiftUI/Modules/Authentication/Common/AuthenticationHomeserverViewData.swift index c5fdf1bb4..7952e7db3 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/AuthenticationHomeserverViewData.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/AuthenticationHomeserverViewData.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,11 +37,11 @@ extension AuthenticationHomeserverViewData { showLoginForm: true, showRegistrationForm: true, ssoIdentityProviders: [ - SSOIdentityProvider(id: "1", name: "Apple", brand: "apple", iconURL: nil), - SSOIdentityProvider(id: "2", name: "Facebook", brand: "facebook", iconURL: nil), - SSOIdentityProvider(id: "3", name: "GitHub", brand: "github", iconURL: nil), - SSOIdentityProvider(id: "4", name: "GitLab", brand: "gitlab", iconURL: nil), - SSOIdentityProvider(id: "5", name: "Google", brand: "google", iconURL: nil) + SSOIdentityProvider(id: "1", name: "Apple", brand: "apple", iconURL: nil), + SSOIdentityProvider(id: "2", name: "Facebook", brand: "facebook", iconURL: nil), + SSOIdentityProvider(id: "3", name: "GitHub", brand: "github", iconURL: nil), + SSOIdentityProvider(id: "4", name: "GitLab", brand: "gitlab", iconURL: nil), + SSOIdentityProvider(id: "5", name: "Google", brand: "google", iconURL: nil) ]) } @@ -68,5 +68,4 @@ extension AuthenticationHomeserverViewData { showRegistrationForm: false, ssoIdentityProviders: []) } - } diff --git a/RiotSwiftUI/Modules/Authentication/Common/AuthenticationModels.swift b/RiotSwiftUI/Modules/Authentication/Common/AuthenticationModels.swift index 1e78da544..e59aa0189 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/AuthenticationModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/AuthenticationModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -78,7 +78,7 @@ enum LoginError: String, Error { case resetPasswordNotStarted } -@objcMembers +@objcMembers class HomeserverAddress: NSObject { /// Sanitizes a user entered homeserver address with the following rules /// - Trim any whitespace. diff --git a/RiotSwiftUI/Modules/Authentication/Common/AuthenticationSSOButton.swift b/RiotSwiftUI/Modules/Authentication/Common/AuthenticationSSOButton.swift index 767ed7d18..6f440d943 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/AuthenticationSSOButton.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/AuthenticationSSOButton.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import SwiftUI /// A button that displays the icon and name of an SSO provider. struct AuthenticationSSOButton: View { - // MARK: - Constants enum Brand: String { diff --git a/RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift b/RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift index edfc703c4..ea62c52c6 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import SwiftUI /// A view that shows information about the chosen homeserver, /// along with an edit button to pick a different one. struct AuthenticationServerInfoSection: View { - // MARK: - Private @Environment(\.theme) private var theme diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationPendingData.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationPendingData.swift index 2aa4bc499..6f637d5b9 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationPendingData.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationPendingData.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationRestClient.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationRestClient.swift index 058ac3d18..33d20f17b 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationRestClient.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationRestClient.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ import Foundation protocol AuthenticationRestClient: AnyObject { // MARK: Configuration + var homeserver: String! { get } var identityServer: String! { get set } var credentials: MXCredentials! { get } @@ -26,24 +27,27 @@ protocol AuthenticationRestClient: AnyObject { init(homeServer: URL, unrecognizedCertificateHandler handler: MXHTTPClientOnUnrecognizedCertificate?) // MARK: Login + var loginFallbackURL: URL { get } func wellKnown() async throws -> MXWellKnown func getLoginSession() async throws -> MXAuthenticationSession func login(parameters: LoginParameters) async throws -> MXCredentials - func login(parameters: [String : Any]) async throws -> MXCredentials + func login(parameters: [String: Any]) async throws -> MXCredentials // MARK: Registration + var registerFallbackURL: URL { get } func getRegisterSession() async throws -> MXAuthenticationSession func isUsernameAvailable(_ username: String) async throws -> Bool func register(parameters: RegistrationParameters) async throws -> MXLoginResponse - func register(parameters: [String : Any]) async throws -> MXLoginResponse + func register(parameters: [String: Any]) async throws -> MXLoginResponse func requestTokenDuringRegistration(for threePID: RegisterThreePID, clientSecret: String, sendAttempt: UInt) async throws -> RegistrationThreePIDTokenResponse // MARK: Forgot Password + func forgetPassword(for email: String, clientSecret: String, sendAttempt: UInt) async throws -> String func resetPassword(parameters: CheckResetPasswordParameters) async throws - func resetPassword(parameters: [String : Any]) async throws + func resetPassword(parameters: [String: Any]) async throws } extension MXRestClient: AuthenticationRestClient { } diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift index eff30724c..492d39834 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,7 +39,6 @@ protocol AuthenticationServiceDelegate: AnyObject { @objcMembers class AuthenticationService: NSObject { - /// The shared service object. static let shared = AuthenticationService() @@ -97,7 +96,7 @@ class AuthenticationService: NSObject { let hsUrl = universalLink.homeserverUrl let isUrl = universalLink.identityServerUrl - if hsUrl == nil && isUrl == nil { + if hsUrl == nil, isUrl == nil { MXLog.debug("[AuthenticationService] handleServerProvisioningLink: no hsUrl or isUrl") return false } @@ -166,7 +165,7 @@ class AuthenticationService: NSObject { // The state and client are set after trying the registration flow to // ensure the existing state isn't wiped out when an error occurs. - self.state = AuthenticationState(flow: flow, homeserver: homeserver) + state = AuthenticationState(flow: flow, homeserver: homeserver) self.client = client } @@ -200,9 +199,9 @@ class AuthenticationService: NSObject { // completeness revert to the default homeserver if requested anyway. let address = useDefaultServer ? BuildSettings.serverConfigDefaultHomeserverUrlString : state.homeserver.addressFromUser ?? state.homeserver.address let identityServer = state.identityServer - self.state = AuthenticationState(flow: .login, - homeserverAddress: address, - identityServer: identityServer) + state = AuthenticationState(flow: .login, + homeserverAddress: address, + identityServer: identityServer) } /// Continues an SSO flow when completion comes via a deep link. @@ -289,7 +288,7 @@ class AuthenticationService: NSObject { let identityProviders = loginFlowResponse.flows?.compactMap { $0 as? MXLoginSSOFlow }.first?.identityProviders ?? [] return LoginFlowResult(supportedLoginTypes: loginFlowResponse.flows?.compactMap { $0 } ?? [], - ssoIdentityProviders: identityProviders.sorted { $0.name < $1.name }.map { $0.ssoIdentityProvider }, + ssoIdentityProviders: identityProviders.sorted { $0.name < $1.name }.map(\.ssoIdentityProvider), homeserverAddress: client.homeserver) } diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationState.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationState.swift index cac59552f..e2a48e315 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationState.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +29,7 @@ struct AuthenticationState { init(flow: AuthenticationFlow, homeserverAddress: String, identityServer: String? = nil) { self.flow = flow - self.homeserver = Homeserver(address: homeserverAddress) + homeserver = Homeserver(address: homeserverAddress) self.identityServer = identityServer } @@ -72,7 +72,7 @@ struct AuthenticationState { /// Needs authentication fallback for login var needsLoginFallback: Bool { - return preferredLoginMode.isUnsupported + preferredLoginMode.isUnsupported } /// Needs authentication fallback for registration diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginModels.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginModels.swift index c3332146c..90c12f503 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -93,4 +93,3 @@ enum LoginMode { struct ResetPasswordData { let addThreePIDSessionID: String } - diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginParameters.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginParameters.swift index 4e4bacb15..6146b3999 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginParameters.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginParameters.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -110,7 +110,7 @@ struct CheckResetPasswordParameters: DictionaryEncodable { } init(clientSecret: String, sessionID: String, newPassword: String, signoutAllDevices: Bool) { - self.auth = AuthenticationParameters.resetPasswordParameters(clientSecret: clientSecret, sessionID: sessionID) + auth = AuthenticationParameters.resetPasswordParameters(clientSecret: clientSecret, sessionID: sessionID) self.newPassword = newPassword self.signoutAllDevices = signoutAllDevices } diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginWizard.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginWizard.swift index ae759b058..f31902506 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginWizard.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginWizard.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,7 +40,7 @@ class LoginWizard { self.client = client self.sessionCreator = sessionCreator - self.state = State() + state = State() } /// Login to the homeserver. diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationModels.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationModels.swift index af3ffbf78..590941353 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -114,7 +114,7 @@ struct FlowResult { } /// Whether fallback registration should be used due to unsupported stages. - var needsFallback : Bool { + var needsFallback: Bool { missingStages.filter(\.isMandatory).contains { stage in if case .other = stage { return true } else { return false } } diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationParameters.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationParameters.swift index d4a1416e9..cdc336e30 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationParameters.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationParameters.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationWizard.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationWizard.swift index 2a2a0989b..76b470ef9 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationWizard.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationWizard.swift @@ -63,7 +63,7 @@ class RegistrationWizard { self.client = client self.sessionCreator = sessionCreator - self.state = State() + state = State() } /// Call this method to get the possible registration flow of the current homeserver. @@ -164,7 +164,7 @@ class RegistrationWizard { /// Send the code received by SMS to validate a msisdn. /// If the code is correct, the registration request will be executed to validate the msisdn. func handleValidateThreePID(code: String) async throws -> RegistrationResult { - return try await validateThreePid(code: code) + try await validateThreePid(code: code) } /// Useful to poll the homeserver when waiting for the email to be validated by the user. @@ -197,7 +197,6 @@ class RegistrationWizard { throw RegistrationError.missingThreePIDURL } - let validationBody = ThreePIDValidationCodeBody(clientSecret: state.clientSecret, sessionID: threePIDData.registrationResponse.sessionID, code: code) @@ -278,7 +277,7 @@ class RegistrationWizard { /// Checks for a dummy stage and handles it automatically when possible. private func handleDummyStage(flowResult: FlowResult) async throws -> RegistrationResult { // If the dummy stage is mandatory, do the dummy stage now - guard flowResult.missingStages.contains(where: { $0.isDummy }) else { return .flowResponse(flowResult) } + guard flowResult.missingStages.contains(where: \.isDummy) else { return .flowResponse(flowResult) } return try await dummy() } diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/SessionCreator.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/SessionCreator.swift index 955f13fed..78d183924 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/SessionCreator.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/SessionCreator.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +29,6 @@ protocol SessionCreatorProtocol { /// A struct that provides common functionality to create a new session. struct SessionCreator: SessionCreatorProtocol { - private let accountManager: MXKAccountManager init(withAccountManager accountManager: MXKAccountManager = .shared()) { @@ -44,7 +43,7 @@ struct SessionCreator: SessionCreatorProtocol { } if removeOtherAccounts { - let otherAccounts = accountManager.accounts.filter({ $0.mxCredentials.userId != credentials.userId }) + let otherAccounts = accountManager.accounts.filter { $0.mxCredentials.userId != credentials.userId } for account in otherAccounts { accountManager.removeAccount(account, completion: nil) } diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/ThreePIDModels.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/ThreePIDModels.swift index 478cbf9f2..852f6a60a 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/ThreePIDModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/ThreePIDModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -53,13 +53,13 @@ struct RegistrationThreePIDTokenResponse { /// /// If this field is not present, the client can assume that verification will happen without the client's involvement provided /// the homeserver advertises this specification version in the /versions response (ie: r0.5.0). - var submitURL: String? = nil + var submitURL: String? // MARK: - Additional data that may be needed - var msisdn: String? = nil - var formattedMSISDN: String? = nil - var success: Bool? = nil + var msisdn: String? + var formattedMSISDN: String? + var success: Bool? enum CodingKeys: String, CodingKey { case sessionID = "sid" diff --git a/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordModels.swift b/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordModels.swift index 788a33552..6d5f60a30 100644 --- a/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordModels.swift +++ b/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordViewModel.swift b/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordViewModel.swift index 461e064a6..fdc17d6c7 100644 --- a/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordViewModel.swift @@ -17,10 +17,9 @@ import SwiftUI typealias AuthenticationForgotPasswordViewModelType = StateStoreViewModel + Never, + AuthenticationForgotPasswordViewAction> class AuthenticationForgotPasswordViewModel: AuthenticationForgotPasswordViewModelType, AuthenticationForgotPasswordViewModelProtocol { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordViewModelProtocol.swift b/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordViewModelProtocol.swift index 673138290..baffd45d1 100644 --- a/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol AuthenticationForgotPasswordViewModelProtocol { - var callback: (@MainActor (AuthenticationForgotPasswordViewModelResult) -> Void)? { get set } var context: AuthenticationForgotPasswordViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Authentication/ForgotPassword/Coordinator/AuthenticationForgotPasswordCoordinator.swift b/RiotSwiftUI/Modules/Authentication/ForgotPassword/Coordinator/AuthenticationForgotPasswordCoordinator.swift index 3f19180cf..deea1e803 100644 --- a/RiotSwiftUI/Modules/Authentication/ForgotPassword/Coordinator/AuthenticationForgotPasswordCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/ForgotPassword/Coordinator/AuthenticationForgotPasswordCoordinator.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI struct AuthenticationForgotPasswordCoordinatorParameters { let navigationRouter: NavigationRouterType @@ -32,7 +32,6 @@ enum AuthenticationForgotPasswordCoordinatorResult { } final class AuthenticationForgotPasswordCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -83,7 +82,7 @@ final class AuthenticationForgotPasswordCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.authenticationForgotPasswordHostingController + authenticationForgotPasswordHostingController } // MARK: - Private diff --git a/RiotSwiftUI/Modules/Authentication/ForgotPassword/MockAuthenticationForgotPasswordScreenState.swift b/RiotSwiftUI/Modules/Authentication/ForgotPassword/MockAuthenticationForgotPasswordScreenState.swift index 1d126e998..e69cbee41 100644 --- a/RiotSwiftUI/Modules/Authentication/ForgotPassword/MockAuthenticationForgotPasswordScreenState.swift +++ b/RiotSwiftUI/Modules/Authentication/ForgotPassword/MockAuthenticationForgotPasswordScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,7 +33,7 @@ enum MockAuthenticationForgotPasswordScreenState: MockScreenState, CaseIterable } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel: AuthenticationForgotPasswordViewModel switch self { case .emptyAddress: diff --git a/RiotSwiftUI/Modules/Authentication/ForgotPassword/Test/UI/AuthenticationForgotPasswordUITests.swift b/RiotSwiftUI/Modules/Authentication/ForgotPassword/Test/UI/AuthenticationForgotPasswordUITests.swift index 06478ce2a..f633349a2 100644 --- a/RiotSwiftUI/Modules/Authentication/ForgotPassword/Test/UI/AuthenticationForgotPasswordUITests.swift +++ b/RiotSwiftUI/Modules/Authentication/ForgotPassword/Test/UI/AuthenticationForgotPasswordUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class AuthenticationForgotPasswordUITests: MockScreenTestCase { func testEmptyAddress() { @@ -98,5 +98,4 @@ class AuthenticationForgotPasswordUITests: MockScreenTestCase { XCTAssertTrue(backButton.exists, "Back button should be shown.") XCTAssertEqual(backButton.label, "Back") } - } diff --git a/RiotSwiftUI/Modules/Authentication/ForgotPassword/Test/Unit/AuthenticationForgotPasswordViewModelTests.swift b/RiotSwiftUI/Modules/Authentication/ForgotPassword/Test/Unit/AuthenticationForgotPasswordViewModelTests.swift index c05dfccaa..925370625 100644 --- a/RiotSwiftUI/Modules/Authentication/ForgotPassword/Test/Unit/AuthenticationForgotPasswordViewModelTests.swift +++ b/RiotSwiftUI/Modules/Authentication/ForgotPassword/Test/Unit/AuthenticationForgotPasswordViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import XCTest @testable import RiotSwiftUI class AuthenticationForgotPasswordViewModelTests: XCTestCase { - var viewModel: AuthenticationForgotPasswordViewModelProtocol! var context: AuthenticationForgotPasswordViewModelType.Context! diff --git a/RiotSwiftUI/Modules/Authentication/ForgotPassword/View/AuthenticationForgotPasswordForm.swift b/RiotSwiftUI/Modules/Authentication/ForgotPassword/View/AuthenticationForgotPasswordForm.swift index 9c19f94fb..815831159 100644 --- a/RiotSwiftUI/Modules/Authentication/ForgotPassword/View/AuthenticationForgotPasswordForm.swift +++ b/RiotSwiftUI/Modules/Authentication/ForgotPassword/View/AuthenticationForgotPasswordForm.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import SwiftUI /// The form shown to enter an email address. struct AuthenticationForgotPasswordForm: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Authentication/ForgotPassword/View/AuthenticationForgotPasswordScreen.swift b/RiotSwiftUI/Modules/Authentication/ForgotPassword/View/AuthenticationForgotPasswordScreen.swift index a2a6619e1..423f4d8b6 100644 --- a/RiotSwiftUI/Modules/Authentication/ForgotPassword/View/AuthenticationForgotPasswordScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/ForgotPassword/View/AuthenticationForgotPasswordScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct AuthenticationForgotPasswordScreen: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift index 823a56499..e273d0d16 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift @@ -57,7 +57,7 @@ struct AuthenticationLoginViewState: BindableState { /// Data about the selected homeserver. var homeserver: AuthenticationHomeserverViewData /// Whether a new homeserver is currently being loaded. - var isLoading: Bool = false + var isLoading = false /// View state that can be bound to from SwiftUI. var bindings: AuthenticationLoginBindings diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift index aa73f0586..d7d528a10 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift @@ -17,11 +17,10 @@ import SwiftUI typealias AuthenticationLoginViewModelType = StateStoreViewModel + Never, + AuthenticationLoginViewAction> class AuthenticationLoginViewModel: AuthenticationLoginViewModelType, AuthenticationLoginViewModelProtocol { - // MARK: - Properties // MARK: Public @@ -52,7 +51,7 @@ class AuthenticationLoginViewModel: AuthenticationLoginViewModelType, Authentica case .fallback: Task { await callback?(.fallback) } case .continueWithSSO(let provider): - Task { await callback?(.continueWithSSO(provider))} + Task { await callback?(.continueWithSSO(provider)) } } } diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModelProtocol.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModelProtocol.swift index afe9d3a92..87ef46fce 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol AuthenticationLoginViewModelProtocol { - var callback: (@MainActor (AuthenticationLoginViewModelResult) -> Void)? { get set } var context: AuthenticationLoginViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift b/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift index 080917097..596e1cad7 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift @@ -14,9 +14,9 @@ // limitations under the License. // -import SwiftUI import CommonKit import MatrixSDK +import SwiftUI struct AuthenticationLoginCoordinatorParameters { let navigationRouter: NavigationRouterType @@ -47,7 +47,6 @@ enum AuthenticationLoginCoordinatorResult: CustomStringConvertible { } final class AuthenticationLoginCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -96,6 +95,7 @@ final class AuthenticationLoginCoordinator: Coordinator, Presentable { } // MARK: - Public + func start() { MXLog.debug("[AuthenticationLoginCoordinator] did start.") Task { await setupViewModel() } diff --git a/RiotSwiftUI/Modules/Authentication/Login/MockAuthenticationLoginScreenState.swift b/RiotSwiftUI/Modules/Authentication/Login/MockAuthenticationLoginScreenState.swift index 1e242c7e1..09ffa9ce6 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/MockAuthenticationLoginScreenState.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/MockAuthenticationLoginScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,7 +35,7 @@ enum MockAuthenticationLoginScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel: AuthenticationLoginViewModel switch self { case .matrixDotOrg: @@ -52,7 +52,6 @@ enum MockAuthenticationLoginScreenState: MockScreenState, CaseIterable { viewModel = AuthenticationLoginViewModel(homeserver: .mockFallback) } - // can simulate service and viewModel actions here if needs be. return ( diff --git a/RiotSwiftUI/Modules/Authentication/Login/Test/UI/AuthenticationLoginUITests.swift b/RiotSwiftUI/Modules/Authentication/Login/Test/UI/AuthenticationLoginUITests.swift index 8dcf6eb63..8075ccc9f 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/Test/UI/AuthenticationLoginUITests.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/Test/UI/AuthenticationLoginUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class AuthenticationLoginUITests: MockScreenTestCase { func testMatrixDotOrg() { @@ -120,5 +120,4 @@ class AuthenticationLoginUITests: MockScreenTestCase { XCTAssertTrue(fallbackButton.exists, "The fallback button should be shown for \(state).") XCTAssertTrue(fallbackButton.isEnabled, "The fallback button should be enabled for \(state).") } - } diff --git a/RiotSwiftUI/Modules/Authentication/Login/Test/Unit/AuthenticationLoginViewModelTests.swift b/RiotSwiftUI/Modules/Authentication/Login/Test/Unit/AuthenticationLoginViewModelTests.swift index dde66c2fb..ae9b3b7ec 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/Test/Unit/AuthenticationLoginViewModelTests.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/Test/Unit/AuthenticationLoginViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift b/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift index 8e4a5776f..3ff67aaf2 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift @@ -17,7 +17,6 @@ import SwiftUI struct AuthenticationLoginScreen: View { - // MARK: - Properties // MARK: Private @@ -52,7 +51,7 @@ struct AuthenticationLoginScreen: View { loginForm } - if viewModel.viewState.homeserver.showLoginForm && viewModel.viewState.showSSOButtons { + if viewModel.viewState.homeserver.showLoginForm, viewModel.viewState.showSSOButtons { Text(VectorL10n.or) .foregroundColor(theme.colors.secondaryContent) .padding(.top, 16) @@ -63,10 +62,9 @@ struct AuthenticationLoginScreen: View { .padding(.top, 16) } - if !viewModel.viewState.homeserver.showLoginForm && !viewModel.viewState.showSSOButtons { + if !viewModel.viewState.homeserver.showLoginForm, !viewModel.viewState.showSSOButtons { fallbackButton } - } .readableFrame() .padding(.horizontal, 16) @@ -104,8 +102,8 @@ struct AuthenticationLoginScreen: View { autocorrectionType: .no), onEditingChanged: usernameEditingChanged, onCommit: { isPasswordFocused = true }) - .accessibilityIdentifier("usernameTextField") - .padding(.bottom, 7) + .accessibilityIdentifier("usernameTextField") + .padding(.bottom, 7) RoundedBorderTextField(placeHolder: VectorL10n.authPasswordPlaceholder, text: $viewModel.password, @@ -114,7 +112,7 @@ struct AuthenticationLoginScreen: View { isSecureTextEntry: true), onEditingChanged: passwordEditingChanged, onCommit: submit) - .accessibilityIdentifier("passwordTextField") + .accessibilityIdentifier("passwordTextField") Button { viewModel.send(viewAction: .forgotPassword) } label: { Text(VectorL10n.authenticationLoginForgotPassword) diff --git a/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaModels.swift b/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaModels.swift index 27b95d713..7c0f90bc5 100644 --- a/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaModels.swift +++ b/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaViewModel.swift b/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaViewModel.swift index 08a733d93..873f278ba 100644 --- a/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaViewModel.swift @@ -17,11 +17,10 @@ import SwiftUI typealias AuthenticationReCaptchaViewModelType = StateStoreViewModel + Never, + AuthenticationReCaptchaViewAction> class AuthenticationReCaptchaViewModel: AuthenticationReCaptchaViewModelType, AuthenticationReCaptchaViewModelProtocol { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaViewModelProtocol.swift b/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaViewModelProtocol.swift index d29a25f13..097af6e9c 100644 --- a/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol AuthenticationReCaptchaViewModelProtocol { - var callback: (@MainActor (AuthenticationReCaptchaViewModelResult) -> Void)? { get set } var context: AuthenticationReCaptchaViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Authentication/ReCaptcha/Coordinator/AuthenticationReCaptchaCoordinator.swift b/RiotSwiftUI/Modules/Authentication/ReCaptcha/Coordinator/AuthenticationReCaptchaCoordinator.swift index 356beb6cb..efeafb184 100644 --- a/RiotSwiftUI/Modules/Authentication/ReCaptcha/Coordinator/AuthenticationReCaptchaCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/ReCaptcha/Coordinator/AuthenticationReCaptchaCoordinator.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI struct AuthenticationReCaptchaCoordinatorParameters { let registrationWizard: RegistrationWizard @@ -26,7 +26,6 @@ struct AuthenticationReCaptchaCoordinatorParameters { } final class AuthenticationReCaptchaCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -76,7 +75,7 @@ final class AuthenticationReCaptchaCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.authenticationReCaptchaHostingController + authenticationReCaptchaHostingController } // MARK: - Private diff --git a/RiotSwiftUI/Modules/Authentication/ReCaptcha/MockAuthenticationReCaptchaScreenState.swift b/RiotSwiftUI/Modules/Authentication/ReCaptcha/MockAuthenticationReCaptchaScreenState.swift index 057ce257f..d96456d0f 100644 --- a/RiotSwiftUI/Modules/Authentication/ReCaptcha/MockAuthenticationReCaptchaScreenState.swift +++ b/RiotSwiftUI/Modules/Authentication/ReCaptcha/MockAuthenticationReCaptchaScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,7 +31,7 @@ enum MockAuthenticationReCaptchaScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel: AuthenticationReCaptchaViewModel switch self { case .standard: diff --git a/RiotSwiftUI/Modules/Authentication/ReCaptcha/Test/UI/AuthenticationReCaptchaUITests.swift b/RiotSwiftUI/Modules/Authentication/ReCaptcha/Test/UI/AuthenticationReCaptchaUITests.swift index ac8ff628c..450d5bfe1 100644 --- a/RiotSwiftUI/Modules/Authentication/ReCaptcha/Test/UI/AuthenticationReCaptchaUITests.swift +++ b/RiotSwiftUI/Modules/Authentication/ReCaptcha/Test/UI/AuthenticationReCaptchaUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class AuthenticationReCaptchaUITests: MockScreenTestCase { // Nothing to test as the view only has a single state. diff --git a/RiotSwiftUI/Modules/Authentication/ReCaptcha/Test/Unit/AuthenticationReCaptchaViewModelTests.swift b/RiotSwiftUI/Modules/Authentication/ReCaptcha/Test/Unit/AuthenticationReCaptchaViewModelTests.swift index 1d8d15b37..37d76e135 100644 --- a/RiotSwiftUI/Modules/Authentication/ReCaptcha/Test/Unit/AuthenticationReCaptchaViewModelTests.swift +++ b/RiotSwiftUI/Modules/Authentication/ReCaptcha/Test/Unit/AuthenticationReCaptchaViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Authentication/ReCaptcha/View/AuthenticationReCaptchaScreen.swift b/RiotSwiftUI/Modules/Authentication/ReCaptcha/View/AuthenticationReCaptchaScreen.swift index 41dd2d896..16dc70bf9 100644 --- a/RiotSwiftUI/Modules/Authentication/ReCaptcha/View/AuthenticationReCaptchaScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/ReCaptcha/View/AuthenticationReCaptchaScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct AuthenticationReCaptchaScreen: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Authentication/ReCaptcha/View/AuthenticationReCaptchaWebView.swift b/RiotSwiftUI/Modules/Authentication/ReCaptcha/View/AuthenticationReCaptchaWebView.swift index fbd000cb5..06e7ff784 100644 --- a/RiotSwiftUI/Modules/Authentication/ReCaptcha/View/AuthenticationReCaptchaWebView.swift +++ b/RiotSwiftUI/Modules/Authentication/ReCaptcha/View/AuthenticationReCaptchaWebView.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import SwiftUI import WebKit struct AuthenticationRecaptchaWebView: UIViewRepresentable { - // MARK: - Properties // MARK: Public @@ -84,7 +83,7 @@ struct AuthenticationRecaptchaWebView: UIViewRepresentable { var completion: ((String) -> Void)? init(isLoading: Binding) { - self._isLoading = isLoading + _isLoading = isLoading } /// Generates the HTML page to show for the given `siteKey` and `theme`. @@ -134,4 +133,3 @@ struct AuthenticationRecaptchaWebView: UIViewRepresentable { } } } - diff --git a/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationModels.swift b/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationModels.swift index 1652a3978..830edf3fc 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,7 +62,7 @@ struct AuthenticationRegistrationViewState: BindableState { /// Data about the selected homeserver. var homeserver: AuthenticationHomeserverViewData /// Whether a new homeserver is currently being loaded. - var isLoading: Bool = false + var isLoading = false /// View state that can be bound to from SwiftUI. var bindings: AuthenticationRegistrationBindings /// Whether or not the username field has been edited yet. diff --git a/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift b/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift index 9b2e1d699..f792adb12 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift @@ -14,15 +14,14 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias AuthenticationRegistrationViewModelType = StateStoreViewModel + Never, + AuthenticationRegistrationViewAction> class AuthenticationRegistrationViewModel: AuthenticationRegistrationViewModelType, AuthenticationRegistrationViewModelProtocol { - // MARK: - Properties // MARK: Public diff --git a/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModelProtocol.swift b/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModelProtocol.swift index 036f720e7..96468ba56 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol AuthenticationRegistrationViewModelProtocol { - var callback: (@MainActor (AuthenticationRegistrationViewModelResult) -> Void)? { get set } var context: AuthenticationRegistrationViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Authentication/Registration/Coordinator/AuthenticationRegistrationCoordinator.swift b/RiotSwiftUI/Modules/Authentication/Registration/Coordinator/AuthenticationRegistrationCoordinator.swift index f749aa32b..72fa8f441 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/Coordinator/AuthenticationRegistrationCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/Coordinator/AuthenticationRegistrationCoordinator.swift @@ -14,9 +14,9 @@ // limitations under the License. // -import SwiftUI import CommonKit import MatrixSDK +import SwiftUI struct AuthenticationRegistrationCoordinatorParameters { let navigationRouter: NavigationRouterType @@ -49,7 +49,6 @@ enum AuthenticationRegistrationCoordinatorResult: CustomStringConvertible { } final class AuthenticationRegistrationCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -97,13 +96,14 @@ final class AuthenticationRegistrationCoordinator: Coordinator, Presentable { } // MARK: - Public + func start() { MXLog.debug("[AuthenticationRegistrationCoordinator] did start.") Task { await setupViewModel() } } func toPresentable() -> UIViewController { - return self.authenticationRegistrationHostingController + authenticationRegistrationHostingController } // MARK: - Private @@ -117,7 +117,7 @@ final class AuthenticationRegistrationCoordinator: Coordinator, Presentable { switch result { case .selectServer: self.presentServerSelectionScreen() - case.validateUsername(let username): + case .validateUsername(let username): self.validateUsername(username) case .createAccount(let username, let password): self.createAccount(username: username, password: password) diff --git a/RiotSwiftUI/Modules/Authentication/Registration/MockAuthenticationRegistrationScreenState.swift b/RiotSwiftUI/Modules/Authentication/Registration/MockAuthenticationRegistrationScreenState.swift index a85021a17..d3086def6 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/MockAuthenticationRegistrationScreenState.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/MockAuthenticationRegistrationScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,7 +36,7 @@ enum MockAuthenticationRegistrationScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel: AuthenticationRegistrationViewModel switch self { case .matrixDotOrg: @@ -58,7 +58,6 @@ enum MockAuthenticationRegistrationScreenState: MockScreenState, CaseIterable { viewModel = AuthenticationRegistrationViewModel(homeserver: .mockFallback) } - // can simulate service and viewModel actions here if needs be. return ( diff --git a/RiotSwiftUI/Modules/Authentication/Registration/Test/UI/AuthenticationRegistrationUITests.swift b/RiotSwiftUI/Modules/Authentication/Registration/Test/UI/AuthenticationRegistrationUITests.swift index 223d20c40..d49ecd580 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/Test/UI/AuthenticationRegistrationUITests.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/Test/UI/AuthenticationRegistrationUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class AuthenticationRegistrationUITests: MockScreenTestCase { func testMatrixDotOrg() { @@ -88,7 +88,6 @@ class AuthenticationRegistrationUITests: MockScreenTestCase { validateFallbackButtonIsShown(for: state) } - /// Checks that the username and password text fields are shown along with the next button. func validateRegistrationFormIsVisible(for state: String) { let usernameTextField = app.textFields.element diff --git a/RiotSwiftUI/Modules/Authentication/Registration/Test/Unit/AuthenticationRegistrationViewModelTests.swift b/RiotSwiftUI/Modules/Authentication/Registration/Test/Unit/AuthenticationRegistrationViewModelTests.swift index 2d5f3830f..734c174f9 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/Test/Unit/AuthenticationRegistrationViewModelTests.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/Test/Unit/AuthenticationRegistrationViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI @@ -110,7 +110,7 @@ import Combine XCTAssertEqual(context.viewState.usernameFooterMessage, VectorL10n.authenticationRegistrationUsernameFooterAvailable("@bob:matrix.org"), "The footer message should display that the username is available.") XCTAssertFalse(context.viewState.isUsernameInvalid, - "The username should continue to be valid when it is available.") + "The username should continue to be valid when it is available.") // When clearing the error. context.send(viewAction: .resetUsernameAvailability) @@ -190,7 +190,7 @@ import Combine // Then the username should be valid but the credentials should still be invalid. XCTAssertTrue(context.viewState.isPasswordInvalid, "An empty password should be invalid.") - XCTAssertFalse(context.viewState.isUsernameInvalid, "The username should be valid when unverified.") + XCTAssertFalse(context.viewState.isUsernameInvalid, "The username should be valid when unverified.") XCTAssertFalse(context.viewState.hasValidCredentials, "The credentials should be invalid.") } diff --git a/RiotSwiftUI/Modules/Authentication/Registration/View/AuthenticationRegistrationScreen.swift b/RiotSwiftUI/Modules/Authentication/Registration/View/AuthenticationRegistrationScreen.swift index 4f62560b3..c333f047a 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/View/AuthenticationRegistrationScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/View/AuthenticationRegistrationScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct AuthenticationRegistrationScreen: View { - // MARK: - Properties // MARK: Private @@ -50,7 +49,7 @@ struct AuthenticationRegistrationScreen: View { registrationForm } - if viewModel.viewState.homeserver.showRegistrationForm && viewModel.viewState.showSSOButtons { + if viewModel.viewState.homeserver.showRegistrationForm, viewModel.viewState.showSSOButtons { Text(VectorL10n.or) .foregroundColor(theme.colors.secondaryContent) .padding(.top, 16) @@ -61,10 +60,9 @@ struct AuthenticationRegistrationScreen: View { .padding(.top, 16) } - if !viewModel.viewState.homeserver.showRegistrationForm && !viewModel.viewState.showSSOButtons { + if !viewModel.viewState.homeserver.showRegistrationForm, !viewModel.viewState.showSSOButtons { fallbackButton } - } .readableFrame() .padding(.horizontal, 16) @@ -110,8 +108,8 @@ struct AuthenticationRegistrationScreen: View { autocorrectionType: .no), onEditingChanged: usernameEditingChanged, onCommit: { isPasswordFocused = true }) - .onChange(of: viewModel.username) { _ in viewModel.send(viewAction: .resetUsernameAvailability) } - .accessibilityIdentifier("usernameTextField") + .onChange(of: viewModel.username) { _ in viewModel.send(viewAction: .resetUsernameAvailability) } + .accessibilityIdentifier("usernameTextField") RoundedBorderTextField(title: nil, placeHolder: VectorL10n.authPasswordPlaceholder, @@ -123,7 +121,7 @@ struct AuthenticationRegistrationScreen: View { isSecureTextEntry: true), onEditingChanged: passwordEditingChanged, onCommit: submit) - .accessibilityIdentifier("passwordTextField") + .accessibilityIdentifier("passwordTextField") Button(action: submit) { Text(VectorL10n.next) diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionModels.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionModels.swift index 8f4a07f62..c94df50ca 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionModels.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift index 8a5326593..fdcc726b5 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift @@ -17,11 +17,10 @@ import SwiftUI typealias AuthenticationServerSelectionViewModelType = StateStoreViewModel + Never, + AuthenticationServerSelectionViewAction> class AuthenticationServerSelectionViewModel: AuthenticationServerSelectionViewModelType, AuthenticationServerSelectionViewModelProtocol { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModelProtocol.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModelProtocol.swift index e57a2d86e..a4e8abe4a 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol AuthenticationServerSelectionViewModelProtocol { - var callback: (@MainActor (AuthenticationServerSelectionViewModelResult) -> Void)? { get set } var context: AuthenticationServerSelectionViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift index 0f3888146..c5d521701 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI struct AuthenticationServerSelectionCoordinatorParameters { let authenticationService: AuthenticationService @@ -31,7 +31,6 @@ enum AuthenticationServerSelectionCoordinatorResult { } final class AuthenticationServerSelectionCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -78,7 +77,7 @@ final class AuthenticationServerSelectionCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.authenticationServerSelectionHostingController + authenticationServerSelectionHostingController } // MARK: - Private diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/MockAuthenticationServerSelectionScreenState.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/MockAuthenticationServerSelectionScreenState.swift index c7e2f12c7..baf8faf53 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/MockAuthenticationServerSelectionScreenState.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/MockAuthenticationServerSelectionScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,7 +35,7 @@ enum MockAuthenticationServerSelectionScreenState: MockScreenState, CaseIterable } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel: AuthenticationServerSelectionViewModel switch self { case .matrix: diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/Test/UI/AuthenticationServerSelectionUITests.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/Test/UI/AuthenticationServerSelectionUITests.swift index e72e00ec4..1bdcd6698 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/Test/UI/AuthenticationServerSelectionUITests.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/Test/UI/AuthenticationServerSelectionUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class AuthenticationServerSelectionUITests: MockScreenTestCase { func testRegisterState() { @@ -41,7 +41,6 @@ class AuthenticationServerSelectionUITests: MockScreenTestCase { XCTAssertTrue(dismissButton.exists, "The dismiss button should be shown during modal presentation.") } - func testLoginState() { app.goToScreenWithIdentifier(MockAuthenticationServerSelectionScreenState.login.title) diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/Test/Unit/AuthenticationServerSelectionViewModelTests.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/Test/Unit/AuthenticationServerSelectionViewModelTests.swift index ed75e67aa..ab62bf4aa 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/Test/Unit/AuthenticationServerSelectionViewModelTests.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/Test/Unit/AuthenticationServerSelectionViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift index 687d6ec92..54144006b 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct AuthenticationServerSelectionScreen: View { - // MARK: - Properties // MARK: Private @@ -37,7 +36,7 @@ struct AuthenticationServerSelectionScreen: View { // MARK: Views var body: some View { - GeometryReader { geometry in + GeometryReader { _ in ScrollView { VStack(spacing: 0) { header diff --git a/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutModels.swift b/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutModels.swift index 7222af762..c6bb92d12 100644 --- a/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutModels.swift +++ b/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,6 +17,7 @@ import SwiftUI // MARK: Data + struct SoftLogoutCredentials { let userId: String let homeserverName: String diff --git a/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutViewModel.swift b/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutViewModel.swift index bd9fcee28..a984ac375 100644 --- a/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutViewModel.swift @@ -17,10 +17,9 @@ import SwiftUI typealias AuthenticationSoftLogoutViewModelType = StateStoreViewModel + Never, + AuthenticationSoftLogoutViewAction> class AuthenticationSoftLogoutViewModel: AuthenticationSoftLogoutViewModelType, AuthenticationSoftLogoutViewModelProtocol { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutViewModelProtocol.swift b/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutViewModelProtocol.swift index f4e948f5b..45f700d41 100644 --- a/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol AuthenticationSoftLogoutViewModelProtocol { - var callback: (@MainActor (AuthenticationSoftLogoutViewModelResult) -> Void)? { get set } var context: AuthenticationSoftLogoutViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Authentication/SoftLogout/Coordinator/AuthenticationSoftLogoutCoordinator.swift b/RiotSwiftUI/Modules/Authentication/SoftLogout/Coordinator/AuthenticationSoftLogoutCoordinator.swift index 5b2fba433..a76ce9a0b 100644 --- a/RiotSwiftUI/Modules/Authentication/SoftLogout/Coordinator/AuthenticationSoftLogoutCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/SoftLogout/Coordinator/AuthenticationSoftLogoutCoordinator.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI struct AuthenticationSoftLogoutCoordinatorParameters { let navigationRouter: NavigationRouterType @@ -50,7 +50,6 @@ enum AuthenticationSoftLogoutCoordinatorResult: CustomStringConvertible { } final class AuthenticationSoftLogoutCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -107,7 +106,7 @@ final class AuthenticationSoftLogoutCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.authenticationSoftLogoutHostingController + authenticationSoftLogoutHostingController } // MARK: - Private diff --git a/RiotSwiftUI/Modules/Authentication/SoftLogout/MockAuthenticationSoftLogoutScreenState.swift b/RiotSwiftUI/Modules/Authentication/SoftLogout/MockAuthenticationSoftLogoutScreenState.swift index 8efa80d67..8127665d5 100644 --- a/RiotSwiftUI/Modules/Authentication/SoftLogout/MockAuthenticationSoftLogoutScreenState.swift +++ b/RiotSwiftUI/Modules/Authentication/SoftLogout/MockAuthenticationSoftLogoutScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,7 +36,7 @@ enum MockAuthenticationSoftLogoutScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel: AuthenticationSoftLogoutViewModel let credentials = SoftLogoutCredentials(userId: "@mock:matrix.org", homeserverName: "matrix.org", diff --git a/RiotSwiftUI/Modules/Authentication/SoftLogout/Test/UI/AuthenticationSoftLogoutUITests.swift b/RiotSwiftUI/Modules/Authentication/SoftLogout/Test/UI/AuthenticationSoftLogoutUITests.swift index 52268610a..4487fee04 100644 --- a/RiotSwiftUI/Modules/Authentication/SoftLogout/Test/UI/AuthenticationSoftLogoutUITests.swift +++ b/RiotSwiftUI/Modules/Authentication/SoftLogout/Test/UI/AuthenticationSoftLogoutUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class AuthenticationSoftLogoutUITests: MockScreenTestCase { func testEmptyPassword() { @@ -185,5 +185,4 @@ class AuthenticationSoftLogoutUITests: MockScreenTestCase { XCTAssertFalse(app.staticTexts["messageLabel2"].exists, "The message 2 should not be shown.") } - } diff --git a/RiotSwiftUI/Modules/Authentication/SoftLogout/Test/Unit/AuthenticationSoftLogoutViewModelTests.swift b/RiotSwiftUI/Modules/Authentication/SoftLogout/Test/Unit/AuthenticationSoftLogoutViewModelTests.swift index 4d9c8142c..bacaf8638 100644 --- a/RiotSwiftUI/Modules/Authentication/SoftLogout/Test/Unit/AuthenticationSoftLogoutViewModelTests.swift +++ b/RiotSwiftUI/Modules/Authentication/SoftLogout/Test/Unit/AuthenticationSoftLogoutViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import XCTest @testable import RiotSwiftUI class AuthenticationSoftLogoutViewModelTests: XCTestCase { - @MainActor func testInitialStateForMatrixOrg() async { let credentials = SoftLogoutCredentials(userId: "mock_user_id", homeserverName: "https://matrix.org", @@ -55,5 +54,4 @@ class AuthenticationSoftLogoutViewModelTests: XCTestCase { XCTAssert(context.viewState.showLoginForm, "The view model should show login form for the given homeserver.") XCTAssertFalse(context.viewState.showRecoverEncryptionKeysMessage, "The view model should not show recover encryption keys message.") } - } diff --git a/RiotSwiftUI/Modules/Authentication/SoftLogout/View/AuthenticationSoftLogoutScreen.swift b/RiotSwiftUI/Modules/Authentication/SoftLogout/View/AuthenticationSoftLogoutScreen.swift index b7d20d88b..ced206e1c 100644 --- a/RiotSwiftUI/Modules/Authentication/SoftLogout/View/AuthenticationSoftLogoutScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/SoftLogout/View/AuthenticationSoftLogoutScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct AuthenticationSoftLogoutScreen: View { - // MARK: - Properties // MARK: Private @@ -153,7 +152,7 @@ struct AuthenticationSoftLogoutScreen: View { configuration: UIKitTextInputConfiguration(returnKeyType: .done, isSecureTextEntry: true), onCommit: login) - .accessibilityIdentifier("passwordTextField") + .accessibilityIdentifier("passwordTextField") } /// A list of SSO buttons that can be used for login. @@ -188,7 +187,6 @@ struct AuthenticationSoftLogoutScreen: View { func clearData() { viewModel.send(viewAction: .clearAllData) } - } // MARK: - Previews diff --git a/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsModels.swift b/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsModels.swift index 8ff128be2..88301c6cb 100644 --- a/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,7 +27,7 @@ struct AuthenticationTermsPolicy: Identifiable, Equatable { /// The policy's subtitle. let subtitle: String /// Whether or not the policy has been accepted. - var accepted: Bool = false + var accepted = false } // MARK: View model diff --git a/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModel.swift b/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModel.swift index e35366899..c868d8f4a 100644 --- a/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModel.swift @@ -16,13 +16,11 @@ import SwiftUI - typealias AuthenticationTermsViewModelType = StateStoreViewModel + Never, + AuthenticationTermsViewAction> class AuthenticationTermsViewModel: AuthenticationTermsViewModelType, AuthenticationTermsViewModelProtocol { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModelProtocol.swift b/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModelProtocol.swift index bd924d7cb..4a5bb4773 100644 --- a/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol AuthenticationTermsViewModelProtocol { - var callback: (@MainActor (AuthenticationTermsViewModelResult) -> Void)? { get set } var context: AuthenticationTermsViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Authentication/Terms/Coordinator/AuthenticationTermsCoordinator.swift b/RiotSwiftUI/Modules/Authentication/Terms/Coordinator/AuthenticationTermsCoordinator.swift index d05e3a5aa..5baeb2d3f 100644 --- a/RiotSwiftUI/Modules/Authentication/Terms/Coordinator/AuthenticationTermsCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/Terms/Coordinator/AuthenticationTermsCoordinator.swift @@ -14,9 +14,9 @@ // limitations under the License. // -import SwiftUI import CommonKit import SafariServices +import SwiftUI struct AuthenticationTermsCoordinatorParameters { let registrationWizard: RegistrationWizard @@ -27,7 +27,6 @@ struct AuthenticationTermsCoordinatorParameters { } final class AuthenticationTermsCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -80,7 +79,7 @@ final class AuthenticationTermsCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.authenticationTermsHostingController + authenticationTermsHostingController } // MARK: - Private diff --git a/RiotSwiftUI/Modules/Authentication/Terms/MockAuthenticationTermsScreenState.swift b/RiotSwiftUI/Modules/Authentication/Terms/MockAuthenticationTermsScreenState.swift index 0bb977c46..7624872e9 100644 --- a/RiotSwiftUI/Modules/Authentication/Terms/MockAuthenticationTermsScreenState.swift +++ b/RiotSwiftUI/Modules/Authentication/Terms/MockAuthenticationTermsScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,7 +33,7 @@ enum MockAuthenticationTermsScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel: AuthenticationTermsViewModel switch self { case .matrixDotOrg: diff --git a/RiotSwiftUI/Modules/Authentication/Terms/Test/UI/AuthenticationTermsUITests.swift b/RiotSwiftUI/Modules/Authentication/Terms/Test/UI/AuthenticationTermsUITests.swift index 3cb31c36a..c9f92a96c 100644 --- a/RiotSwiftUI/Modules/Authentication/Terms/Test/UI/AuthenticationTermsUITests.swift +++ b/RiotSwiftUI/Modules/Authentication/Terms/Test/UI/AuthenticationTermsUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class AuthenticationTermsUITests: MockScreenTestCase { func testMatrixDotOrg() { @@ -38,5 +38,4 @@ class AuthenticationTermsUITests: MockScreenTestCase { XCTAssertTrue(nextButton.exists, "The next button should always exist.") XCTAssertEqual(nextButton.isEnabled, accepted, "The next button should be enabled when the terms are accepted") } - } diff --git a/RiotSwiftUI/Modules/Authentication/Terms/Test/Unit/AuthenticationTermsViewModelTests.swift b/RiotSwiftUI/Modules/Authentication/Terms/Test/Unit/AuthenticationTermsViewModelTests.swift index 70166d010..ccbc50721 100644 --- a/RiotSwiftUI/Modules/Authentication/Terms/Test/Unit/AuthenticationTermsViewModelTests.swift +++ b/RiotSwiftUI/Modules/Authentication/Terms/Test/Unit/AuthenticationTermsViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Authentication/Terms/View/AuthenticationTermsListItem.swift b/RiotSwiftUI/Modules/Authentication/Terms/View/AuthenticationTermsListItem.swift index d8d8456b3..9cdb3d1d9 100644 --- a/RiotSwiftUI/Modules/Authentication/Terms/View/AuthenticationTermsListItem.swift +++ b/RiotSwiftUI/Modules/Authentication/Terms/View/AuthenticationTermsListItem.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct AuthenticationTermsListItem: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Authentication/Terms/View/AuthenticationTermsScreen.swift b/RiotSwiftUI/Modules/Authentication/Terms/View/AuthenticationTermsScreen.swift index d8241ef62..ff2aa02cf 100644 --- a/RiotSwiftUI/Modules/Authentication/Terms/View/AuthenticationTermsScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/Terms/View/AuthenticationTermsScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct AuthenticationTermsScreen: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Authentication/Terms/View/AuthenticationTermsToggleStyle.swift b/RiotSwiftUI/Modules/Authentication/Terms/View/AuthenticationTermsToggleStyle.swift index b8bdec32f..f5e94ce84 100644 --- a/RiotSwiftUI/Modules/Authentication/Terms/View/AuthenticationTermsToggleStyle.swift +++ b/RiotSwiftUI/Modules/Authentication/Terms/View/AuthenticationTermsToggleStyle.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailModels.swift b/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailModels.swift index dfe29221b..c3fce72fe 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailModels.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModel.swift b/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModel.swift index fca58b047..269c5e0a1 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModel.swift @@ -17,10 +17,9 @@ import SwiftUI typealias AuthenticationVerifyEmailViewModelType = StateStoreViewModel + Never, + AuthenticationVerifyEmailViewAction> class AuthenticationVerifyEmailViewModel: AuthenticationVerifyEmailViewModelType, AuthenticationVerifyEmailViewModelProtocol { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModelProtocol.swift b/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModelProtocol.swift index 1c72824e5..6700cd157 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol AuthenticationVerifyEmailViewModelProtocol { - var callback: (@MainActor (AuthenticationVerifyEmailViewModelResult) -> Void)? { get set } var context: AuthenticationVerifyEmailViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Authentication/VerifyEmail/Coordinator/AuthenticationVerifyEmailCoordinator.swift b/RiotSwiftUI/Modules/Authentication/VerifyEmail/Coordinator/AuthenticationVerifyEmailCoordinator.swift index bd68b0e44..920884048 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyEmail/Coordinator/AuthenticationVerifyEmailCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyEmail/Coordinator/AuthenticationVerifyEmailCoordinator.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI struct AuthenticationVerifyEmailCoordinatorParameters { let registrationWizard: RegistrationWizard @@ -24,7 +24,6 @@ struct AuthenticationVerifyEmailCoordinatorParameters { } final class AuthenticationVerifyEmailCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -74,7 +73,7 @@ final class AuthenticationVerifyEmailCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.authenticationVerifyEmailHostingController + authenticationVerifyEmailHostingController } // MARK: - Private diff --git a/RiotSwiftUI/Modules/Authentication/VerifyEmail/MockAuthenticationVerifyEmailScreenState.swift b/RiotSwiftUI/Modules/Authentication/VerifyEmail/MockAuthenticationVerifyEmailScreenState.swift index 3226f2f00..2474d00cc 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyEmail/MockAuthenticationVerifyEmailScreenState.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyEmail/MockAuthenticationVerifyEmailScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,7 +33,7 @@ enum MockAuthenticationVerifyEmailScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel: AuthenticationVerifyEmailViewModel switch self { case .emptyAddress: diff --git a/RiotSwiftUI/Modules/Authentication/VerifyEmail/Test/UI/AuthenticationVerifyEmailUITests.swift b/RiotSwiftUI/Modules/Authentication/VerifyEmail/Test/UI/AuthenticationVerifyEmailUITests.swift index 73706abcd..52dad31a0 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyEmail/Test/UI/AuthenticationVerifyEmailUITests.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyEmail/Test/UI/AuthenticationVerifyEmailUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class AuthenticationVerifyEmailUITests: MockScreenTestCase { func testEmptyAddress() { @@ -78,5 +78,4 @@ class AuthenticationVerifyEmailUITests: MockScreenTestCase { XCTAssertTrue(backButton.exists, "Back button should be shown.") XCTAssertEqual(backButton.label, "Back") } - } diff --git a/RiotSwiftUI/Modules/Authentication/VerifyEmail/Test/Unit/AuthenticationVerifyEmailViewModelTests.swift b/RiotSwiftUI/Modules/Authentication/VerifyEmail/Test/Unit/AuthenticationVerifyEmailViewModelTests.swift index 3b340a9bf..553858d25 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyEmail/Test/Unit/AuthenticationVerifyEmailViewModelTests.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyEmail/Test/Unit/AuthenticationVerifyEmailViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import XCTest @testable import RiotSwiftUI class AuthenticationVerifyEmailViewModelTests: XCTestCase { - var viewModel: AuthenticationVerifyEmailViewModelProtocol! var context: AuthenticationVerifyEmailViewModelType.Context! diff --git a/RiotSwiftUI/Modules/Authentication/VerifyEmail/View/AuthenticationVerifyEmailForm.swift b/RiotSwiftUI/Modules/Authentication/VerifyEmail/View/AuthenticationVerifyEmailForm.swift index 274a0a942..79ca40c12 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyEmail/View/AuthenticationVerifyEmailForm.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyEmail/View/AuthenticationVerifyEmailForm.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import SwiftUI /// The form shown to enter an email address. struct AuthenticationVerifyEmailForm: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Authentication/VerifyEmail/View/AuthenticationVerifyEmailScreen.swift b/RiotSwiftUI/Modules/Authentication/VerifyEmail/View/AuthenticationVerifyEmailScreen.swift index 97283a9f9..2081f58d9 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyEmail/View/AuthenticationVerifyEmailScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyEmail/View/AuthenticationVerifyEmailScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct AuthenticationVerifyEmailScreen: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnModels.swift b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnModels.swift index c9d9aec6e..a89016fda 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnModels.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModel.swift b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModel.swift index de688374a..651a4935a 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModel.swift @@ -17,10 +17,9 @@ import SwiftUI typealias AuthenticationVerifyMsisdnViewModelType = StateStoreViewModel + Never, + AuthenticationVerifyMsisdnViewAction> class AuthenticationVerifyMsisdnViewModel: AuthenticationVerifyMsisdnViewModelType, AuthenticationVerifyMsisdnViewModelProtocol { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModelProtocol.swift b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModelProtocol.swift index 56c565398..b9412cd46 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol AuthenticationVerifyMsisdnViewModelProtocol { - var callback: (@MainActor (AuthenticationVerifyMsisdnViewModelResult) -> Void)? { get set } var context: AuthenticationVerifyMsisdnViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/Coordinator/AuthenticationVerifyMsisdnCoordinator.swift b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/Coordinator/AuthenticationVerifyMsisdnCoordinator.swift index 1962efe56..0d7b42dc4 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/Coordinator/AuthenticationVerifyMsisdnCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/Coordinator/AuthenticationVerifyMsisdnCoordinator.swift @@ -14,9 +14,9 @@ // limitations under the License. // -import SwiftUI import CommonKit import libPhoneNumber_iOS +import SwiftUI struct AuthenticationVerifyMsisdnCoordinatorParameters { let registrationWizard: RegistrationWizard @@ -25,7 +25,6 @@ struct AuthenticationVerifyMsisdnCoordinatorParameters { } final class AuthenticationVerifyMsisdnCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -75,7 +74,7 @@ final class AuthenticationVerifyMsisdnCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.authenticationVerifyMsisdnHostingController + authenticationVerifyMsisdnHostingController } // MARK: - Private @@ -174,7 +173,6 @@ final class AuthenticationVerifyMsisdnCoordinator: Coordinator, Presentable { self?.stopLoading() self?.handleError(error) } - } } diff --git a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/MockAuthenticationVerifyMsisdnScreenState.swift b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/MockAuthenticationVerifyMsisdnScreenState.swift index 63242fa42..622176a74 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/MockAuthenticationVerifyMsisdnScreenState.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/MockAuthenticationVerifyMsisdnScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,7 +34,7 @@ enum MockAuthenticationVerifyMsisdnScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel: AuthenticationVerifyMsisdnViewModel switch self { case .emptyPhoneNumber: diff --git a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/Test/UI/AuthenticationVerifyMsisdnUITests.swift b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/Test/UI/AuthenticationVerifyMsisdnUITests.swift index f5528acfa..535565860 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/Test/UI/AuthenticationVerifyMsisdnUITests.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/Test/UI/AuthenticationVerifyMsisdnUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class AuthenticationVerifyMsisdnUITests: MockScreenTestCase { func testEmptyPhoneNumber() { @@ -127,5 +127,4 @@ class AuthenticationVerifyMsisdnUITests: MockScreenTestCase { XCTAssertTrue(backButton.exists, "Back button should be shown.") XCTAssertEqual(backButton.label, "Back") } - } diff --git a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/Test/Unit/AuthenticationVerifyMsisdnViewModelTests.swift b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/Test/Unit/AuthenticationVerifyMsisdnViewModelTests.swift index 77a117d37..fb4c1965c 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/Test/Unit/AuthenticationVerifyMsisdnViewModelTests.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/Test/Unit/AuthenticationVerifyMsisdnViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import XCTest @testable import RiotSwiftUI class AuthenticationVerifyMsisdnViewModelTests: XCTestCase { - var viewModel: AuthenticationVerifyMsisdnViewModelProtocol! var context: AuthenticationVerifyMsisdnViewModelType.Context! diff --git a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/View/AuthenticationVerifyMsisdnForm.swift b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/View/AuthenticationVerifyMsisdnForm.swift index 9701a680d..97a7b6f20 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/View/AuthenticationVerifyMsisdnForm.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/View/AuthenticationVerifyMsisdnForm.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import SwiftUI /// The form shown to enter an email address. struct AuthenticationVerifyMsisdnForm: View { - // MARK: - Properties // MARK: Private @@ -65,7 +64,7 @@ struct AuthenticationVerifyMsisdnForm: View { /// The text field and submit button where the user enters a phone number. var mainContent: some View { - return VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: 12) { if #available(iOS 15.0, *) { textField .onSubmit(sendSMS) diff --git a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/View/AuthenticationVerifyMsisdnOTPForm.swift b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/View/AuthenticationVerifyMsisdnOTPForm.swift index e56badab4..926892791 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/View/AuthenticationVerifyMsisdnOTPForm.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/View/AuthenticationVerifyMsisdnOTPForm.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import SwiftUI /// The form shown to enter an OTP for phone number vaildation struct AuthenticationVerifyMsisdnOTPForm: View { - // MARK: - Properties // MARK: Private @@ -65,7 +64,7 @@ struct AuthenticationVerifyMsisdnOTPForm: View { /// The text field and submit button where the user enters an OTP. var mainContent: some View { - return VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: 12) { if #available(iOS 15.0, *) { textField .onSubmit(submitOTP) @@ -93,7 +92,7 @@ struct AuthenticationVerifyMsisdnOTPForm: View { /// The text field, extracted for iOS 15 modifiers to be applied. var textField: some View { - return TextField(VectorL10n.authenticationVerifyMsisdnOtpTextFieldPlaceholder, text: $viewModel.otp) { + TextField(VectorL10n.authenticationVerifyMsisdnOtpTextFieldPlaceholder, text: $viewModel.otp) { isEditingTextField = $0 } .textFieldStyle(BorderedInputFieldStyle(isEditing: isEditingTextField, isError: false)) @@ -104,7 +103,7 @@ struct AuthenticationVerifyMsisdnOTPForm: View { /// Sends the `submitOTP` view action so long as a valid OTP has been input. func submitOTP() { - guard viewModel.viewState.hasSentSMS && !viewModel.viewState.hasInvalidOTP else { return } + guard viewModel.viewState.hasSentSMS, !viewModel.viewState.hasInvalidOTP else { return } viewModel.send(viewAction: .submitOTP) } } diff --git a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/View/AuthenticationVerifyMsisdnScreen.swift b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/View/AuthenticationVerifyMsisdnScreen.swift index 867e58224..c6bd7943d 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/View/AuthenticationVerifyMsisdnScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/View/AuthenticationVerifyMsisdnScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct AuthenticationVerifyMsisdnScreen: View { - // MARK: - Properties // MARK: Private @@ -31,7 +30,7 @@ struct AuthenticationVerifyMsisdnScreen: View { // MARK: Views var body: some View { - GeometryReader { geometry in + GeometryReader { _ in VStack { ScrollView { if viewModel.viewState.hasSentSMS { diff --git a/RiotSwiftUI/Modules/Common/ActivityIndicator/ActivityIndicator.swift b/RiotSwiftUI/Modules/Common/ActivityIndicator/ActivityIndicator.swift index 51c4ecdd8..32b48c821 100644 --- a/RiotSwiftUI/Modules/Common/ActivityIndicator/ActivityIndicator.swift +++ b/RiotSwiftUI/Modules/Common/ActivityIndicator/ActivityIndicator.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import SwiftUI /// A visual cue to user that something is in progress. struct ActivityIndicator: View { - private enum Constants { static let backgroundColor = Color(UIColor(white: 0.8, alpha: 0.9)) } @@ -29,7 +28,6 @@ struct ActivityIndicator: View { .padding() .background(Constants.backgroundColor) .cornerRadius(5) - } } diff --git a/RiotSwiftUI/Modules/Common/ActivityIndicator/ActivityIndicatorModifier.swift b/RiotSwiftUI/Modules/Common/ActivityIndicator/ActivityIndicatorModifier.swift index cd4bb859f..3d935ea67 100644 --- a/RiotSwiftUI/Modules/Common/ActivityIndicator/ActivityIndicatorModifier.swift +++ b/RiotSwiftUI/Modules/Common/ActivityIndicator/ActivityIndicatorModifier.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,6 +37,6 @@ struct ActivityIndicatorModifier: ViewModifier { extension View { func activityIndicator(show: Bool) -> some View { - self.modifier(ActivityIndicatorModifier(show: show)) + modifier(ActivityIndicatorModifier(show: show)) } } diff --git a/RiotSwiftUI/Modules/Common/Avatar/Model/AvatarInputProtocol.swift b/RiotSwiftUI/Modules/Common/Avatar/Model/AvatarInputProtocol.swift index 2b36660a0..fe53976f1 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/Model/AvatarInputProtocol.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/Model/AvatarInputProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Common/Avatar/Model/AvatarProtocol.swift b/RiotSwiftUI/Modules/Common/Avatar/Model/AvatarProtocol.swift index 7963c333a..b3342b8b3 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/Model/AvatarProtocol.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/Model/AvatarProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Common/Avatar/Model/Avatarable.swift b/RiotSwiftUI/Modules/Common/Avatar/Model/Avatarable.swift index 4e5062b5f..ce8e4ee40 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/Model/Avatarable.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/Model/Avatarable.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ import Foundation /// A protocol that any class or struct can conform to /// so that it can easily produce avatar data. -/// +/// /// E.g. MXRoom, MxUser can conform to this making it /// easy to grab the avatar data for display. protocol Avatarable: AvatarInputProtocol { } diff --git a/RiotSwiftUI/Modules/Common/Avatar/Model/Mock/MockAvatarInput.swift b/RiotSwiftUI/Modules/Common/Avatar/Model/Mock/MockAvatarInput.swift index 4e62c0b50..773c6d439 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/Model/Mock/MockAvatarInput.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/Model/Mock/MockAvatarInput.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Common/Avatar/Service/MatrixSDK/AvatarService.swift b/RiotSwiftUI/Modules/Common/Avatar/Service/MatrixSDK/AvatarService.swift index 2e6ca5a94..02369980c 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/Service/MatrixSDK/AvatarService.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/Service/MatrixSDK/AvatarService.swift @@ -14,10 +14,10 @@ // limitations under the License. // -import Foundation -import MatrixSDK import Combine import DesignKit +import Foundation +import MatrixSDK enum AvatarServiceError: Error { case pathNotfound @@ -25,7 +25,6 @@ enum AvatarServiceError: Error { } class AvatarService: AvatarServiceProtocol { - private enum Constants { static let mimeType = "image/jpeg" static let thumbnailMethod = MXThumbnailingMethodCrop @@ -34,7 +33,7 @@ class AvatarService: AvatarServiceProtocol { private let mediaManager: MXMediaManager static func instantiate(mediaManager: MXMediaManager) -> AvatarServiceProtocol { - return AvatarService(mediaManager: mediaManager) + AvatarService(mediaManager: mediaManager) } init(mediaManager: MXMediaManager) { @@ -49,13 +48,13 @@ class AvatarService: AvatarServiceProtocol { /// - avatarSize: The size of avatar to retrieve as defined in the DesignKit spec. /// - Returns: A Future of UIImage that returns an error if it fails to fetch the image. func avatarImage(mxContentUri: String, avatarSize: AvatarSize) -> Future { - let cachePath = MXMediaManager.thumbnailCachePath( forMatrixContentURI: mxContentUri, andType: Constants.mimeType, - inFolder: nil, + inFolder: nil, toFitViewSize: avatarSize.size, - with: Constants.thumbnailMethod) + with: Constants.thumbnailMethod + ) return Future { promise in if let image = MXMediaManager.loadThroughCache(withFilePath: cachePath), @@ -69,7 +68,8 @@ class AvatarService: AvatarServiceProtocol { withType: Constants.mimeType, inFolder: nil, toFitViewSize: avatarSize.size, - with: Constants.thumbnailMethod) { path in + with: Constants.thumbnailMethod + ) { path in guard let path = path else { promise(.failure(AvatarServiceError.pathNotfound)) return diff --git a/RiotSwiftUI/Modules/Common/Avatar/Service/Mock/MockAvatarService.swift b/RiotSwiftUI/Modules/Common/Avatar/Service/Mock/MockAvatarService.swift index 94ea01c55..eff0be3f4 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/Service/Mock/MockAvatarService.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/Service/Mock/MockAvatarService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,9 +14,9 @@ // limitations under the License. // -import Foundation import Combine import DesignKit +import Foundation import UIKit class MockAvatarService: AvatarServiceProtocol { diff --git a/RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift b/RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift index 2b7fa9e60..4f51f574a 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,11 +14,10 @@ // limitations under the License. // -import SwiftUI import DesignKit +import SwiftUI struct AvatarImage: View { - @Environment(\.theme) var theme: ThemeSwiftUI @Environment(\.dependencies) var dependencies: DependencyContainer @StateObject var viewModel = AvatarViewModel() @@ -75,7 +74,7 @@ extension AvatarImage { /// Use display name color as border color by default func border() -> some View { let borderColor = theme.userColor(for: matrixItemId) - return self.border(color: borderColor) + return border(color: borderColor) } } diff --git a/RiotSwiftUI/Modules/Common/Avatar/View/PlaceholderAvatarImage.swift b/RiotSwiftUI/Modules/Common/Avatar/View/PlaceholderAvatarImage.swift index f119a7e14..55a1b6854 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/View/PlaceholderAvatarImage.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/View/PlaceholderAvatarImage.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,6 @@ import SwiftUI /// This view has a forced 1:1 aspect ratio but will appear very large until a `.frame` /// modifier is applied. struct PlaceholderAvatarImage: View { - // MARK: - Private @Environment(\.theme) private var theme diff --git a/RiotSwiftUI/Modules/Common/Avatar/View/SpaceAvatarImage.swift b/RiotSwiftUI/Modules/Common/Avatar/View/SpaceAvatarImage.swift index d82e2107f..8e967fe18 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/View/SpaceAvatarImage.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/View/SpaceAvatarImage.swift @@ -14,11 +14,10 @@ // limitations under the License. // -import SwiftUI import DesignKit +import SwiftUI struct SpaceAvatarImage: View { - @Environment(\.theme) var theme: ThemeSwiftUI @Environment(\.dependencies) var dependencies: DependencyContainer @StateObject var viewModel = AvatarViewModel() diff --git a/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarServiceProtocol.swift b/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarServiceProtocol.swift index 1f505a7db..578c34834 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarServiceProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,12 +14,11 @@ // limitations under the License. // -import Foundation -import DesignKit import Combine +import DesignKit +import Foundation import UIKit - /// Provides a simple api to retrieve and cache avatar images protocol AvatarServiceProtocol { func avatarImage(mxContentUri: String, avatarSize: AvatarSize) -> Future diff --git a/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewModel.swift b/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewModel.swift index 90f736da6..433fb9cba 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewModel.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewModel.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,13 +14,12 @@ // limitations under the License. // -import Foundation import Combine import DesignKit +import Foundation /// Simple ViewModel that supports loading an avatar image class AvatarViewModel: InjectableObject, ObservableObject { - @Inject var avatarService: AvatarServiceProtocol @Published private(set) var viewState = AvatarViewState.empty @@ -34,24 +33,22 @@ class AvatarViewModel: InjectableObject, ObservableObject { /// - displayName: Display name of the avatar. /// - colorCount: The count of total avatar colors used to generate the stable color index. /// - avatarSize: The size of the avatar to fetch (as defined within DesignKit). - func loadAvatar( - mxContentUri: String?, - matrixItemId: String, - displayName: String?, - colorCount: Int, - avatarSize: AvatarSize) { - + func loadAvatar(mxContentUri: String?, + matrixItemId: String, + displayName: String?, + colorCount: Int, + avatarSize: AvatarSize) { let placeholderViewModel = PlaceholderAvatarViewModel(displayName: displayName, matrixItemId: matrixItemId, colorCount: colorCount) - self.viewState = .placeholder(placeholderViewModel.firstCharacterCapitalized, placeholderViewModel.stableColorIndex) + viewState = .placeholder(placeholderViewModel.firstCharacterCapitalized, placeholderViewModel.stableColorIndex) guard let mxContentUri = mxContentUri, mxContentUri.count > 0 else { return } - avatarService.avatarImage(mxContentUri: mxContentUri, avatarSize: avatarSize) + avatarService.avatarImage(mxContentUri: mxContentUri, avatarSize: avatarSize) .sink { completion in guard case let .failure(error) = completion else { return } UILog.error("[AvatarService] Failed to retrieve avatar", context: error) diff --git a/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewState.swift b/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewState.swift index cac2a70d4..97b09ea41 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewState.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Common/Avatar/ViewModel/PlaceholderAvatarViewModel.swift b/RiotSwiftUI/Modules/Common/Avatar/ViewModel/PlaceholderAvatarViewModel.swift index d5c131b39..618d6f284 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/ViewModel/PlaceholderAvatarViewModel.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/ViewModel/PlaceholderAvatarViewModel.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,7 +27,7 @@ struct PlaceholderAvatarViewModel { /// Get the first character of the display name capitalized or else a space character. var firstCharacterCapitalized: Character { - return displayName?.capitalized.first ?? " " + displayName?.capitalized.first ?? " " } /// Provides the same color each time for a specified matrixId @@ -39,7 +39,7 @@ struct PlaceholderAvatarViewModel { var stableColorIndex: Int { // Sum all characters let sum = matrixItemId.utf8 - .map({ UInt($0) }) + .map { UInt($0) } .reduce(0, +) // modulo the color count return Int(sum) % colorCount diff --git a/RiotSwiftUI/Modules/Common/Bridging/VectorContentView.swift b/RiotSwiftUI/Modules/Common/Bridging/VectorContentView.swift index 4783fd01c..dfdd5e8b3 100644 --- a/RiotSwiftUI/Modules/Common/Bridging/VectorContentView.swift +++ b/RiotSwiftUI/Modules/Common/Bridging/VectorContentView.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,6 @@ import SwiftUI /// /// Provides any app level configuration the SwiftUI hierarchy might need (E.g. to monitor theme changes). struct VectorContentModifier: ViewModifier { - @ObservedObject private var themePublisher = ThemePublisher.shared @Environment(\.layoutDirection) private var defaultLayoutDirection @@ -39,6 +38,6 @@ struct VectorContentModifier: ViewModifier { extension View { func vectorContent() -> some View { - self.modifier(VectorContentModifier()) + modifier(VectorContentModifier()) } } diff --git a/RiotSwiftUI/Modules/Common/DependencyInjection/DependencyContainer.swift b/RiotSwiftUI/Modules/Common/DependencyInjection/DependencyContainer.swift index c3c0169fd..d09fa87f4 100644 --- a/RiotSwiftUI/Modules/Common/DependencyInjection/DependencyContainer.swift +++ b/RiotSwiftUI/Modules/Common/DependencyInjection/DependencyContainer.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import Foundation /// Used for storing and resolving dependencies at runtime. struct DependencyContainer { - // Stores the dependencies with type information removed. private var dependencyStore: [String: Any] = [:] @@ -26,7 +25,7 @@ struct DependencyContainer { /// /// Given a particular `Type` (Inferred from return type), /// generate a key and retrieve from storage. - /// + /// /// - Returns: The resolved dependency. func resolve() -> T { let key = String(describing: T.self) diff --git a/RiotSwiftUI/Modules/Common/DependencyInjection/DependencyContainerKey.swift b/RiotSwiftUI/Modules/Common/DependencyInjection/DependencyContainerKey.swift index b788b362d..4bde8956e 100644 --- a/RiotSwiftUI/Modules/Common/DependencyInjection/DependencyContainerKey.swift +++ b/RiotSwiftUI/Modules/Common/DependencyInjection/DependencyContainerKey.swift @@ -33,7 +33,6 @@ extension EnvironmentValues { } extension View { - /// A modifier for adding a dependency to the SwiftUI view hierarchy's dependency container. /// /// Important: When adding a dependency to cast it to the type in which it will be injected. diff --git a/RiotSwiftUI/Modules/Common/DependencyInjection/Inject.swift b/RiotSwiftUI/Modules/Common/DependencyInjection/Inject.swift index e81457678..d45907eeb 100644 --- a/RiotSwiftUI/Modules/Common/DependencyInjection/Inject.swift +++ b/RiotSwiftUI/Modules/Common/DependencyInjection/Inject.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,12 +22,9 @@ import Foundation /// @Inject var someClass: SomeClass /// ``` @propertyWrapper struct Inject { - - static subscript( - _enclosingInstance instance: T, - wrapped wrappedKeyPath: ReferenceWritableKeyPath, - storage storageKeyPath: ReferenceWritableKeyPath - ) -> Value { + static subscript(_enclosingInstance instance: T, + wrapped wrappedKeyPath: ReferenceWritableKeyPath, + storage storageKeyPath: ReferenceWritableKeyPath) -> Value { get { // Resolve dependencies from enclosing instance's `dependencies` property let v: Value = instance.dependencies.resolve() @@ -38,9 +35,9 @@ import Foundation } } - @available(*, unavailable, message: "This property wrapper can only be applied to classes") + @available(*, unavailable, message: "This property wrapper can only be applied to classes") var wrappedValue: Value { get { fatalError("wrappedValue get not used") } - set { fatalError("wrappedValue set not used. \(newValue)" ) } + set { fatalError("wrappedValue set not used. \(newValue)") } } } diff --git a/RiotSwiftUI/Modules/Common/DependencyInjection/Injectable.swift b/RiotSwiftUI/Modules/Common/DependencyInjection/Injectable.swift index 96e5eef64..b05b966e4 100644 --- a/RiotSwiftUI/Modules/Common/DependencyInjection/Injectable.swift +++ b/RiotSwiftUI/Modules/Common/DependencyInjection/Injectable.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,9 +21,7 @@ protocol Injectable: AnyObject { var dependencies: DependencyContainer! { get set } } - extension Injectable { - /// Used to inject the dependency container into an Injectable. /// - Parameter dependencies: The `DependencyContainer` to inject. func inject(dependencies: DependencyContainer) { diff --git a/RiotSwiftUI/Modules/Common/DependencyInjection/InjectableObject.swift b/RiotSwiftUI/Modules/Common/DependencyInjection/InjectableObject.swift index bf38a0707..eab3cdcdf 100644 --- a/RiotSwiftUI/Modules/Common/DependencyInjection/InjectableObject.swift +++ b/RiotSwiftUI/Modules/Common/DependencyInjection/InjectableObject.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Common/EffectsScene/EffectsScene.swift b/RiotSwiftUI/Modules/Common/EffectsScene/EffectsScene.swift index 38eb5db11..6224c7185 100644 --- a/RiotSwiftUI/Modules/Common/EffectsScene/EffectsScene.swift +++ b/RiotSwiftUI/Modules/Common/EffectsScene/EffectsScene.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import SceneKit import SwiftUI class EffectsScene: SCNScene { - // MARK: - Constants private enum Constants { @@ -31,14 +30,14 @@ class EffectsScene: SCNScene { static func confetti(with theme: ThemeSwiftUI) -> EffectsScene? { guard let scene = EffectsScene(named: Constants.confettiSceneName) else { return nil } - let colors: [[Float]] = theme.colors.namesAndAvatars.compactMap { $0.floatComponents } + let colors: [[Float]] = theme.colors.namesAndAvatars.compactMap(\.floatComponents) if let particles = scene.rootNode.childNode(withName: Constants.particlesNodeName, recursively: false)?.particleSystems?.first { // The particles need a non-zero color variation for the handler to affect the color particles.particleColorVariation = SCNVector4(x: 0, y: 0, z: 0, w: 0.1) // Add a handler to customize the color of the particles. - particles.handle(.birth, forProperties: [.color]) { data, dataStride, indices, count in + particles.handle(.birth, forProperties: [.color]) { data, dataStride, _, count in for index in 0..: Identifiable { /// The alert's title. let title: String /// The alert's message (optional). - var message: String? = nil + var message: String? /// The alert's primary button title and action. Defaults to an Ok button with no action. var primaryButton: (title: String, action: (() -> Void)?) = (VectorL10n.ok, nil) /// The alert's secondary button title and action. - var secondaryButton: (title: String, action: (() -> Void)?)? = nil + var secondaryButton: (title: String, action: (() -> Void)?)? } extension AlertInfo { @@ -47,7 +47,7 @@ extension AlertInfo { /// - id: An ID that identifies the error. /// - error: The Error that occurred. init?(id: T, error: NSError? = nil) { - guard error?.domain != NSURLErrorDomain && error?.code != NSURLErrorCancelled else { return nil } + guard error?.domain != NSURLErrorDomain, error?.code != NSURLErrorCancelled else { return nil } self.id = id title = error?.userInfo[NSLocalizedFailureReasonErrorKey] as? String ?? VectorL10n.error diff --git a/RiotSwiftUI/Modules/Common/Extensions/CLLocationCoordinate2D.swift b/RiotSwiftUI/Modules/Common/Extensions/CLLocationCoordinate2D.swift index cdc21e100..7a5fdf63c 100644 --- a/RiotSwiftUI/Modules/Common/Extensions/CLLocationCoordinate2D.swift +++ b/RiotSwiftUI/Modules/Common/Extensions/CLLocationCoordinate2D.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,18 +14,16 @@ // limitations under the License. // -import Foundation import CoreLocation +import Foundation extension CLLocationCoordinate2D { - /// Compare two coordinates /// - parameter coordinate: another coordinate to compare /// - parameter precision:it represente how close you want the two coordinates /// - return: bool value func isEqual(to coordinate: CLLocationCoordinate2D, precision: Double) -> Bool { - - if fabs(self.latitude - coordinate.latitude) <= precision && fabs(self.longitude - coordinate.longitude) <= precision { + if fabs(latitude - coordinate.latitude) <= precision, fabs(longitude - coordinate.longitude) <= precision { return true } return false diff --git a/RiotSwiftUI/Modules/Common/Extensions/MatrixSDK/MXRoomAvatarable.swift b/RiotSwiftUI/Modules/Common/Extensions/MatrixSDK/MXRoomAvatarable.swift index e18feed41..ac7a7ebbc 100644 --- a/RiotSwiftUI/Modules/Common/Extensions/MatrixSDK/MXRoomAvatarable.swift +++ b/RiotSwiftUI/Modules/Common/Extensions/MatrixSDK/MXRoomAvatarable.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,5 +27,4 @@ extension MXRoom: Avatarable { var displayName: String? { summary.displayname } - } diff --git a/RiotSwiftUI/Modules/Common/Extensions/MatrixSDK/MXUserAvatarable.swift b/RiotSwiftUI/Modules/Common/Extensions/MatrixSDK/MXUserAvatarable.swift index 701c2f75f..f6843b063 100644 --- a/RiotSwiftUI/Modules/Common/Extensions/MatrixSDK/MXUserAvatarable.swift +++ b/RiotSwiftUI/Modules/Common/Extensions/MatrixSDK/MXUserAvatarable.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,5 +27,4 @@ extension MXUser: Avatarable { var displayName: String? { displayname } - } diff --git a/RiotSwiftUI/Modules/Common/Extensions/Publisher.swift b/RiotSwiftUI/Modules/Common/Extensions/Publisher.swift index 892608cef..c5e3622ad 100644 --- a/RiotSwiftUI/Modules/Common/Extensions/Publisher.swift +++ b/RiotSwiftUI/Modules/Common/Extensions/Publisher.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,17 +14,15 @@ // limitations under the License. // -import Foundation import Combine +import Foundation extension Publisher where Failure == Never { /// Same as `assign(to:on:)` but maintains a weak reference to object /// /// Useful in cases where you want to pass self and not cause a retain cycle. - func weakAssign( - to keyPath: ReferenceWritableKeyPath, - on object: T - ) -> AnyCancellable { + func weakAssign(to keyPath: ReferenceWritableKeyPath, + on object: T) -> AnyCancellable { sink { [weak object] value in object?[keyPath: keyPath] = value } diff --git a/RiotSwiftUI/Modules/Common/Locale/LocaleProvider.swift b/RiotSwiftUI/Modules/Common/Locale/LocaleProvider.swift index a1ba74921..11f44e8be 100644 --- a/RiotSwiftUI/Modules/Common/Locale/LocaleProvider.swift +++ b/RiotSwiftUI/Modules/Common/Locale/LocaleProvider.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,6 @@ import Foundation class LocaleProvider: LocaleProviderType { static var locale: Locale? { - return nil + nil } } diff --git a/RiotSwiftUI/Modules/Common/Logging/LoggerProtocol.swift b/RiotSwiftUI/Modules/Common/Logging/LoggerProtocol.swift index bedcb6a67..6f2eabbf1 100644 --- a/RiotSwiftUI/Modules/Common/Logging/LoggerProtocol.swift +++ b/RiotSwiftUI/Modules/Common/Logging/LoggerProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Common/Logging/PrintLogger.swift b/RiotSwiftUI/Modules/Common/Logging/PrintLogger.swift index 85587467f..decebbc42 100644 --- a/RiotSwiftUI/Modules/Common/Logging/PrintLogger.swift +++ b/RiotSwiftUI/Modules/Common/Logging/PrintLogger.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,15 +23,19 @@ class PrintLogger: LoggerProtocol { static func verbose(_ message: @autoclosure () -> Any, _ file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil) { print(message()) } + static func debug(_ message: @autoclosure () -> Any, _ file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil) { print(message()) } + static func info(_ message: @autoclosure () -> Any, _ file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil) { print(message()) } + static func warning(_ message: @autoclosure () -> Any, _ file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil) { print(message()) } + static func error(_ message: @autoclosure () -> StaticString, _ file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil) { print(message()) } diff --git a/RiotSwiftUI/Modules/Common/Logging/UILog.swift b/RiotSwiftUI/Modules/Common/Logging/UILog.swift index ee0c4f22b..97fc113e4 100644 --- a/RiotSwiftUI/Modules/Common/Logging/UILog.swift +++ b/RiotSwiftUI/Modules/Common/Logging/UILog.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,54 +20,48 @@ import Foundation /// /// It can be configured at runtime with a suitable logger. class UILog: LoggerProtocol { - static var _logger: LoggerProtocol.Type? static func configure(logger: LoggerProtocol.Type) { _logger = logger } - static func verbose( - _ message: @autoclosure () -> Any, - _ file: String = #file, - _ function: String = #function, - line: Int = #line, - context: Any? = nil) { + static func verbose(_ message: @autoclosure () -> Any, + _ file: String = #file, + _ function: String = #function, + line: Int = #line, + context: Any? = nil) { _logger?.verbose(message(), file, function, line: line, context: context) } - static func debug( - _ message: @autoclosure () -> Any, - _ file: String = #file, - _ function: String = #function, - line: Int = #line, - context: Any? = nil) { + static func debug(_ message: @autoclosure () -> Any, + _ file: String = #file, + _ function: String = #function, + line: Int = #line, + context: Any? = nil) { _logger?.debug(message(), file, function, line: line, context: context) } - static func info( - _ message: @autoclosure () -> Any, - _ file: String = #file, - _ function: String = #function, - line: Int = #line, - context: Any? = nil) { + static func info(_ message: @autoclosure () -> Any, + _ file: String = #file, + _ function: String = #function, + line: Int = #line, + context: Any? = nil) { _logger?.info(message(), file, function, line: line, context: context) } - static func warning( - _ message: @autoclosure () -> Any, - _ file: String = #file, - _ function: String = #function, - line: Int = #line, - context: Any? = nil) { + static func warning(_ message: @autoclosure () -> Any, + _ file: String = #file, + _ function: String = #function, + line: Int = #line, + context: Any? = nil) { _logger?.warning(message(), file, function, line: line, context: context) } - static func error( - _ message: @autoclosure () -> StaticString, - _ file: String = #file, - _ function: String = #function, - line: Int = #line, - context: Any? = nil) { + static func error(_ message: @autoclosure () -> StaticString, + _ file: String = #file, + _ function: String = #function, + line: Int = #line, + context: Any? = nil) { _logger?.error(message(), file, function, line: line, context: context) } } diff --git a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift index 8d5a62e63..05cc50234 100644 --- a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift +++ b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -63,4 +63,3 @@ enum MockAppScreens { MockSpaceSelectorScreenState.self ] } - diff --git a/RiotSwiftUI/Modules/Common/Mock/MockScreenState.swift b/RiotSwiftUI/Modules/Common/Mock/MockScreenState.swift index 073d32381..0129807cd 100644 --- a/RiotSwiftUI/Modules/Common/Mock/MockScreenState.swift +++ b/RiotSwiftUI/Modules/Common/Mock/MockScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,12 +24,11 @@ protocol MockScreenState { } extension MockScreenState { - /// Get a list of the screens for every screen state. static var stateRenderer: StateRenderer { - let depsAndViews = screenStates.map(\.screenView) - let deps = depsAndViews.map({ $0.0 }) - let views = depsAndViews.map({ $0.1 }) + let depsAndViews = screenStates.map(\.screenView) + let deps = depsAndViews.map(\.0) + let views = depsAndViews.map(\.1) let titles = screenStates.map(\.title) var states = [ScreenStateInfo]() @@ -45,7 +44,7 @@ extension MockScreenState { /// All available screen state keys static var screenNames: [String] { - screenStates.map { $0.title } + screenStates.map(\.title) } /// A title to represent the screen and it's screen state @@ -54,12 +53,12 @@ extension MockScreenState { } private func simpleTypeName(_ type: Any) -> String { - String(describing: type).components(separatedBy: .punctuationCharacters).filter { $0.count > 0}.last! + String(describing: type).components(separatedBy: .punctuationCharacters).filter { $0.count > 0 }.last! } } extension MockScreenState where Self: CaseIterable { static var screenStates: [MockScreenState] { - return Array(self.allCases) + Array(allCases) } } diff --git a/RiotSwiftUI/Modules/Common/Mock/ScreenList.swift b/RiotSwiftUI/Modules/Common/Mock/ScreenList.swift index 6f7f221b3..91cf8937f 100644 --- a/RiotSwiftUI/Modules/Common/Mock/ScreenList.swift +++ b/RiotSwiftUI/Modules/Common/Mock/ScreenList.swift @@ -13,11 +13,9 @@ // See the License for the specific language governing permissions and // limitations under the License. - import SwiftUI struct ScreenList: View { - private let allStates: [ScreenStateInfo] @State private var searchQuery = "" @@ -25,8 +23,9 @@ struct ScreenList: View { init(screens: [MockScreenState.Type]) { let states = screens + // swiftformat:disable:next preferKeyPath .map { $0.stateRenderer } - .flatMap { $0.states } + .flatMap(\.states) allStates = states filteredStates = states diff --git a/RiotSwiftUI/Modules/Common/Mock/ScreenStateInfo.swift b/RiotSwiftUI/Modules/Common/Mock/ScreenStateInfo.swift index 6ebc6f591..0534c80dc 100644 --- a/RiotSwiftUI/Modules/Common/Mock/ScreenStateInfo.swift +++ b/RiotSwiftUI/Modules/Common/Mock/ScreenStateInfo.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Common/Mock/StateRenderer.swift b/RiotSwiftUI/Modules/Common/Mock/StateRenderer.swift index 207146794..627a54803 100644 --- a/RiotSwiftUI/Modules/Common/Mock/StateRenderer.swift +++ b/RiotSwiftUI/Modules/Common/Mock/StateRenderer.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,7 +35,7 @@ class StateRenderer { addNavigation: Bool = false ) -> some View { Group { - ForEach(0..(_ wrap: Bool, view: V) -> some View { if wrap { - NavigationView{ + NavigationView { view .navigationBarTitleDisplayMode(.inline) } diff --git a/RiotSwiftUI/Modules/Common/Test/UI/MockScreenTest.swift b/RiotSwiftUI/Modules/Common/Test/UI/MockScreenTest.swift index 3c00337d1..d5be4d0e4 100644 --- a/RiotSwiftUI/Modules/Common/Test/UI/MockScreenTest.swift +++ b/RiotSwiftUI/Modules/Common/Test/UI/MockScreenTest.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest /// XCTestCase subclass to ease testing of `MockScreenState`. /// Launches the app with an environment variable used to disable animations. @@ -26,7 +26,7 @@ import RiotSwiftUI class MockScreenTestCase: XCTestCase { let app = XCUIApplication() - open override func setUpWithError() throws { + override open func setUpWithError() throws { app.launchEnvironment = ["IS_RUNNING_UI_TESTS": "1"] app.launch() } diff --git a/RiotSwiftUI/Modules/Common/Test/UI/XCUIApplication+Riot.swift b/RiotSwiftUI/Modules/Common/Test/UI/XCUIApplication+Riot.swift index 09e5799f7..a2e7dc2b5 100644 --- a/RiotSwiftUI/Modules/Common/Test/UI/XCUIApplication+Riot.swift +++ b/RiotSwiftUI/Modules/Common/Test/UI/XCUIApplication+Riot.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,11 +23,11 @@ extension XCUIApplication { textFields["searchQueryTextField"].tap() typeText(identifier) - let button = self.buttons[identifier] + let button = buttons[identifier] let footer = staticTexts["footerText"] - while !button.isHittable && !footer.isHittable { - self.tables.firstMatch.swipeUp() + while !button.isHittable, !footer.isHittable { + tables.firstMatch.swipeUp() } button.tap() diff --git a/RiotSwiftUI/Modules/Common/Test/XCTestPublisherExtensions.swift b/RiotSwiftUI/Modules/Common/Test/XCTestPublisherExtensions.swift index 11db89923..d749ec858 100644 --- a/RiotSwiftUI/Modules/Common/Test/XCTestPublisherExtensions.swift +++ b/RiotSwiftUI/Modules/Common/Test/XCTestPublisherExtensions.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import Combine +import XCTest extension XCTestCase { /// XCTest utility to wait for results from publishers, so that the output can be used for assertions. @@ -29,11 +29,9 @@ extension XCTestCase { /// - timeout: A timeout after which we give up. /// - Throws: If it can't get the unwrapped result. /// - Returns: The unwrapped result. - func xcAwait( - _ publisher: T, - timeout: TimeInterval = 10 - ) throws -> T.Output { - return try xcAwaitDeferred(publisher, timeout: timeout)() + func xcAwait(_ publisher: T, + timeout: TimeInterval = 10) throws -> T.Output { + try xcAwaitDeferred(publisher, timeout: timeout)() } /// XCTest utility that allows for a deferred wait of results from publishers, so that the output can be used for assertions. @@ -48,12 +46,10 @@ extension XCTestCase { /// - publisher: The publisher to wait on. /// - timeout: A timeout after which we give up. /// - Returns: A closure that starts the waiting of results when called. The closure will return the unwrapped result. - func xcAwaitDeferred( - _ publisher: T, - timeout: TimeInterval = 10 - ) -> (() throws -> (T.Output)) { + func xcAwaitDeferred(_ publisher: T, + timeout: TimeInterval = 10) -> (() throws -> (T.Output)) { var result: Result? - let expectation = self.expectation(description: "Awaiting publisher") + let expectation = expectation(description: "Awaiting publisher") let cancellable = publisher.sink( receiveCompletion: { completion in diff --git a/RiotSwiftUI/Modules/Common/Theme/ThemeIdentifierExtensions.swift b/RiotSwiftUI/Modules/Common/Theme/ThemeIdentifierExtensions.swift index d5401d27e..90a05a540 100644 --- a/RiotSwiftUI/Modules/Common/Theme/ThemeIdentifierExtensions.swift +++ b/RiotSwiftUI/Modules/Common/Theme/ThemeIdentifierExtensions.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import Foundation import DesignKit +import Foundation extension ThemeIdentifier { fileprivate static let defaultTheme = DefaultThemeSwiftUI() diff --git a/RiotSwiftUI/Modules/Common/Theme/ThemeKey.swift b/RiotSwiftUI/Modules/Common/Theme/ThemeKey.swift index a2e742b7f..79403c0dc 100644 --- a/RiotSwiftUI/Modules/Common/Theme/ThemeKey.swift +++ b/RiotSwiftUI/Modules/Common/Theme/ThemeKey.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,35 +14,35 @@ // limitations under the License. // +import DesignKit import Foundation import SwiftUI -import DesignKit private struct ThemeKey: EnvironmentKey { static let defaultValue = ThemePublisher.shared.theme } extension EnvironmentValues { - var theme: ThemeSwiftUI { - get { self[ThemeKey.self] } - set { self[ThemeKey.self] = newValue } - } + var theme: ThemeSwiftUI { + get { self[ThemeKey.self] } + set { self[ThemeKey.self] = newValue } + } } extension View { /// A theme modifier for setting the theme for this view and all its descendants in the hierarchy. /// - Parameter theme: A theme to be set as the environment value. /// - Returns: The target view with the theme applied. - func theme(_ theme: ThemeSwiftUI) -> some View { - environment(\.theme, theme) - } + func theme(_ theme: ThemeSwiftUI) -> some View { + environment(\.theme, theme) + } } extension View { /// A theme modifier for setting the theme by id for this view and all its descendants in the hierarchy. /// - Parameter themeId: ThemeIdentifier of a theme to be set as the environment value. /// - Returns: The target view with the theme applied. - func theme(_ themeId: ThemeIdentifier) -> some View { - return environment(\.theme, themeId.themeSwiftUI) - } + func theme(_ themeId: ThemeIdentifier) -> some View { + environment(\.theme, themeId.themeSwiftUI) + } } diff --git a/RiotSwiftUI/Modules/Common/Theme/ThemePublisher.swift b/RiotSwiftUI/Modules/Common/Theme/ThemePublisher.swift index ff0a39eca..1dccf7058 100644 --- a/RiotSwiftUI/Modules/Common/Theme/ThemePublisher.swift +++ b/RiotSwiftUI/Modules/Common/Theme/ThemePublisher.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,21 +14,20 @@ // limitations under the License. // -import Foundation import Combine +import Foundation /// Provides the theme and theme updates to SwiftUI. /// /// Replaces the old ThemeObserver. Riot app can push updates to this class /// removing the dependency of this class on the `ThemeService`. class ThemePublisher: ObservableObject { - - private static var _shared: ThemePublisher? = nil + private static var _shared: ThemePublisher? static var shared: ThemePublisher { - if _shared == nil { - configure(themeId: .light) - } - return _shared! + if _shared == nil { + configure(themeId: .light) + } + return _shared! } @Published private(set) var theme: ThemeSwiftUI @@ -38,7 +37,7 @@ class ThemePublisher: ObservableObject { } init(themeId: ThemeIdentifier) { - _theme = Published.init(initialValue: themeId.themeSwiftUI) + _theme = Published(initialValue: themeId.themeSwiftUI) } func republish(themeIdPublisher: AnyPublisher) { diff --git a/RiotSwiftUI/Modules/Common/Theme/ThemeSwiftUI.swift b/RiotSwiftUI/Modules/Common/Theme/ThemeSwiftUI.swift index f5a15424f..930338076 100644 --- a/RiotSwiftUI/Modules/Common/Theme/ThemeSwiftUI.swift +++ b/RiotSwiftUI/Modules/Common/Theme/ThemeSwiftUI.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import Foundation import DesignKit +import Foundation protocol ThemeSwiftUI: ThemeSwiftUIType { var identifier: ThemeIdentifier { get } diff --git a/RiotSwiftUI/Modules/Common/Theme/ThemeUsersColorsExtension.swift b/RiotSwiftUI/Modules/Common/Theme/ThemeUsersColorsExtension.swift index ad1eeb222..a75c2f2be 100644 --- a/RiotSwiftUI/Modules/Common/Theme/ThemeUsersColorsExtension.swift +++ b/RiotSwiftUI/Modules/Common/Theme/ThemeUsersColorsExtension.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import Foundation import SwiftUI extension ThemeSwiftUI { - /// Get the stable display user color based on userId. /// - Parameter userId: The user id used to hash. /// - Returns: The SwiftUI color for the associated userId. diff --git a/RiotSwiftUI/Modules/Common/Theme/Themes/DarkThemeSwiftUI.swift b/RiotSwiftUI/Modules/Common/Theme/Themes/DarkThemeSwiftUI.swift index 0e9250070..f418a0203 100644 --- a/RiotSwiftUI/Modules/Common/Theme/Themes/DarkThemeSwiftUI.swift +++ b/RiotSwiftUI/Modules/Common/Theme/Themes/DarkThemeSwiftUI.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,12 +14,12 @@ // limitations under the License. // -import Foundation import DesignKit +import Foundation struct DarkThemeSwiftUI: ThemeSwiftUI { var identifier: ThemeIdentifier = .dark - let isDark: Bool = true + let isDark = true var colors: ColorSwiftUI = DarkColors.swiftUI - var fonts: FontSwiftUI = FontSwiftUI(values: ElementFonts()) + var fonts = FontSwiftUI(values: ElementFonts()) } diff --git a/RiotSwiftUI/Modules/Common/Theme/Themes/DefaultThemeSwiftUI.swift b/RiotSwiftUI/Modules/Common/Theme/Themes/DefaultThemeSwiftUI.swift index 85ba4d810..d0b12fd79 100644 --- a/RiotSwiftUI/Modules/Common/Theme/Themes/DefaultThemeSwiftUI.swift +++ b/RiotSwiftUI/Modules/Common/Theme/Themes/DefaultThemeSwiftUI.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,12 +14,12 @@ // limitations under the License. // -import Foundation import DesignKit +import Foundation struct DefaultThemeSwiftUI: ThemeSwiftUI { var identifier: ThemeIdentifier = .light - let isDark: Bool = false + let isDark = false var colors: ColorSwiftUI = LightColors.swiftUI - var fonts: FontSwiftUI = FontSwiftUI(values: ElementFonts()) + var fonts = FontSwiftUI(values: ElementFonts()) } diff --git a/RiotSwiftUI/Modules/Common/Util/BorderModifier.swift b/RiotSwiftUI/Modules/Common/Util/BorderModifier.swift index 6d5ad7aef..f88e8141f 100644 --- a/RiotSwiftUI/Modules/Common/Util/BorderModifier.swift +++ b/RiotSwiftUI/Modules/Common/Util/BorderModifier.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct BorderModifier: ViewModifier { - var color: Color var borderWidth: CGFloat var shape: Shape diff --git a/RiotSwiftUI/Modules/Common/Util/BorderedInputFieldStyle.swift b/RiotSwiftUI/Modules/Common/Util/BorderedInputFieldStyle.swift index fe75aa300..6859e4712 100644 --- a/RiotSwiftUI/Modules/Common/Util/BorderedInputFieldStyle.swift +++ b/RiotSwiftUI/Modules/Common/Util/BorderedInputFieldStyle.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,20 +15,19 @@ // import Foundation -import SwiftUI import Introspect +import SwiftUI /// A bordered style of text input /// /// As defined in: /// https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=2039%3A26415 struct BorderedInputFieldStyle: TextFieldStyle { - @Environment(\.theme) private var theme: ThemeSwiftUI @Environment(\.isEnabled) private var isEnabled: Bool - var isEditing: Bool = false - var isError: Bool = false + var isEditing = false + var isError = false private var borderColor: Color { if isError { @@ -47,7 +46,7 @@ struct BorderedInputFieldStyle: TextFieldStyle { } private var textColor: Color { - if (theme.identifier == ThemeIdentifier.dark) { + if theme.identifier == ThemeIdentifier.dark { return (isEnabled ? theme.colors.primaryContent : theme.colors.tertiaryContent) } else { return (isEnabled ? theme.colors.primaryContent : theme.colors.quarterlyContent) @@ -55,18 +54,18 @@ struct BorderedInputFieldStyle: TextFieldStyle { } private var backgroundColor: Color { - if !isEnabled && (theme.identifier == ThemeIdentifier.dark) { + if !isEnabled, theme.identifier == ThemeIdentifier.dark { return theme.colors.quinaryContent } return theme.colors.background } private var placeholderColor: Color { - return theme.colors.tertiaryContent + theme.colors.tertiaryContent } private var borderWidth: CGFloat { - return isEditing || isError ? 2.0 : 1.5 + isEditing || isError ? 2.0 : 1.5 } func _body(configuration: TextField<_Label>) -> some View { diff --git a/RiotSwiftUI/Modules/Common/Util/ClearViewModifier.swift b/RiotSwiftUI/Modules/Common/Util/ClearViewModifier.swift index 7eb67d39c..209c61aa1 100644 --- a/RiotSwiftUI/Modules/Common/Util/ClearViewModifier.swift +++ b/RiotSwiftUI/Modules/Common/Util/ClearViewModifier.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ import SwiftUI extension ThemableTextEditor { func showClearButton(text: Binding, alignment: VerticalAlignment = .top) -> some View { - return modifier(ClearViewModifier(alignment: alignment, text: text)) + modifier(ClearViewModifier(alignment: alignment, text: text)) } } diff --git a/RiotSwiftUI/Modules/Common/Util/InlineTextButton.swift b/RiotSwiftUI/Modules/Common/Util/InlineTextButton.swift index fa394b827..e5673b12c 100644 --- a/RiotSwiftUI/Modules/Common/Util/InlineTextButton.swift +++ b/RiotSwiftUI/Modules/Common/Util/InlineTextButton.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import SwiftUI @available(iOS, introduced: 14.0, deprecated: 15.0, message: "Use Text with an AttributedString instead that includes a link and handle the tap by adding an OpenURLAction to the environment.") /// A `Button`, that fakes having a tappable string inside of a regular string. struct InlineTextButton: View { - private struct StringComponent { let string: Substring let isTinted: Bool @@ -33,7 +32,6 @@ struct InlineTextButton: View { private let components: [StringComponent] private let action: () -> Void - // MARK: - Setup /// Creates a new `InlineTextButton`. @@ -43,7 +41,7 @@ struct InlineTextButton: View { /// - action: The action to perform when tapping the button. internal init(_ mainText: String, tappableText: String, action: @escaping () -> Void) { guard let range = mainText.range(of: "%@") else { - self.components = [StringComponent(string: Substring(mainText), isTinted: false)] + components = [StringComponent(string: Substring(mainText), isTinted: false)] self.action = action return } @@ -52,7 +50,7 @@ struct InlineTextButton: View { let middleComponent = StringComponent(string: Substring(tappableText), isTinted: true) let lastComponent = StringComponent(string: mainText[range.upperBound...], isTinted: false) - self.components = [firstComponent, middleComponent, lastComponent] + components = [firstComponent, middleComponent, lastComponent] self.action = action } @@ -63,7 +61,7 @@ struct InlineTextButton: View { EmptyView() } .buttonStyle(Style(components: components)) - .accessibilityLabel(components.map { $0.string }.joined()) + .accessibilityLabel(components.map(\.string).joined()) } private struct Style: ButtonStyle { diff --git a/RiotSwiftUI/Modules/Common/Util/MultilineTextField.swift b/RiotSwiftUI/Modules/Common/Util/MultilineTextField.swift index 5e20f11b0..08e1712ab 100644 --- a/RiotSwiftUI/Modules/Common/Util/MultilineTextField.swift +++ b/RiotSwiftUI/Modules/Common/Util/MultilineTextField.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,14 +17,13 @@ import SwiftUI struct MultilineTextField: View { - @Environment(\.theme) private var theme: ThemeSwiftUI @Binding private var text: String @State private var dynamicHeight: CGFloat = 100 @State private var isEditing = false - private var placeholder: String = "" + private var placeholder = "" private var showingPlaceholder: Bool { text.isEmpty @@ -32,11 +31,11 @@ struct MultilineTextField: View { init(_ placeholder: String, text: Binding) { self.placeholder = placeholder - self._text = text + _text = text } private var textColor: Color { - if (theme.identifier == ThemeIdentifier.dark) { + if theme.identifier == ThemeIdentifier.dark { return theme.colors.primaryContent } else { return theme.colors.primaryContent @@ -44,11 +43,11 @@ struct MultilineTextField: View { } private var backgroundColor: Color { - return theme.colors.background + theme.colors.background } private var placeholderColor: Color { - return theme.colors.tertiaryContent + theme.colors.tertiaryContent } private var borderColor: Color { @@ -60,7 +59,7 @@ struct MultilineTextField: View { } private var borderWidth: CGFloat { - return isEditing ? 2.0 : 1.5 + isEditing ? 2.0 : 1.5 } var body: some View { @@ -91,7 +90,7 @@ struct MultilineTextField: View { } } -fileprivate struct UITextViewWrapper: UIViewRepresentable { +private struct UITextViewWrapper: UIViewRepresentable { typealias UIViewType = UITextView @Binding var text: String @@ -115,8 +114,8 @@ fileprivate struct UITextViewWrapper: UIViewRepresentable { } func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext) { - if uiView.text != self.text { - uiView.text = self.text + if uiView.text != text { + uiView.text = text } UITextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight) @@ -132,7 +131,7 @@ fileprivate struct UITextViewWrapper: UIViewRepresentable { } func makeCoordinator() -> Coordinator { - return Coordinator(text: $text, height: $calculatedHeight, isEditing: $isEditing) + Coordinator(text: $text, height: $calculatedHeight, isEditing: $isEditing) } final class Coordinator: NSObject, UITextViewDelegate { @@ -142,7 +141,7 @@ fileprivate struct UITextViewWrapper: UIViewRepresentable { init(text: Binding, height: Binding, isEditing: Binding) { self.text = text - self.calculatedHeight = height + calculatedHeight = height self.isEditing = isEditing } @@ -171,9 +170,8 @@ fileprivate struct UITextViewWrapper: UIViewRepresentable { } struct MultilineTextField_Previews: PreviewProvider { - static var previews: some View { - return Group { + Group { VStack { PreviewWrapper() PlaceholderPreviewWrapper() diff --git a/RiotSwiftUI/Modules/Common/Util/OptionButton.swift b/RiotSwiftUI/Modules/Common/Util/OptionButton.swift index 17e54bbda..7758ae5c7 100644 --- a/RiotSwiftUI/Modules/Common/Util/OptionButton.swift +++ b/RiotSwiftUI/Modules/Common/Util/OptionButton.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,11 +17,9 @@ import SwiftUI struct OptionButton: View { - // MARK: - Style private struct Style: ButtonStyle { - func makeBody(configuration: Configuration) -> some View { configuration.label .scaleEffect(configuration.isPressed ? 0.97 : 1) @@ -61,8 +59,7 @@ struct OptionButton: View { .background(theme.colors.quinaryContent) .foregroundColor(theme.colors.secondaryContent) .clipShape(RoundedRectangle(cornerRadius: 8)) - } - ) + }) .buttonStyle(Style()) } } @@ -73,14 +70,14 @@ struct OptionButton_Previews: PreviewProvider { static var previews: some View { Group { VStack { - OptionButton(icon: Asset.Images.spaceTypeIcon.image, title: "A title", detailMessage: "Some details for this option", action: {}).theme(.light) - OptionButton(icon: nil, title: "A title", detailMessage: "Some details for this option", action: {}).theme(.light) - OptionButton(icon: nil, title: "A title", detailMessage: nil, action: {}).theme(.light) + OptionButton(icon: Asset.Images.spaceTypeIcon.image, title: "A title", detailMessage: "Some details for this option", action: { }).theme(.light) + OptionButton(icon: nil, title: "A title", detailMessage: "Some details for this option", action: { }).theme(.light) + OptionButton(icon: nil, title: "A title", detailMessage: nil, action: { }).theme(.light) } VStack { - OptionButton(icon: Asset.Images.spaceTypeIcon.image, title: "A title", detailMessage: "Some details for this option", action: {}).theme(.dark) - OptionButton(icon: nil, title: "A title", detailMessage: "Some details for this option", action: {}).theme(.dark) - OptionButton(icon: nil, title: "A title", detailMessage: nil, action: {}).theme(.dark) + OptionButton(icon: Asset.Images.spaceTypeIcon.image, title: "A title", detailMessage: "Some details for this option", action: { }).theme(.dark) + OptionButton(icon: nil, title: "A title", detailMessage: "Some details for this option", action: { }).theme(.dark) + OptionButton(icon: nil, title: "A title", detailMessage: nil, action: { }).theme(.dark) }.preferredColorScheme(.dark) } .padding() diff --git a/RiotSwiftUI/Modules/Common/Util/PasswordButtonModifier.swift b/RiotSwiftUI/Modules/Common/Util/PasswordButtonModifier.swift index 94fd8525a..0e0f38586 100644 --- a/RiotSwiftUI/Modules/Common/Util/PasswordButtonModifier.swift +++ b/RiotSwiftUI/Modules/Common/Util/PasswordButtonModifier.swift @@ -19,7 +19,6 @@ import SwiftUI /// Adds a reveal password button (e.g. an eye button) on the /// right side of the view. For use with `ThemableTextField`. struct PasswordButtonModifier: ViewModifier { - // MARK: - Properties let text: String diff --git a/RiotSwiftUI/Modules/Common/Util/PrimaryActionButtonStyle.swift b/RiotSwiftUI/Modules/Common/Util/PrimaryActionButtonStyle.swift index 461f232ff..07a07f49a 100644 --- a/RiotSwiftUI/Modules/Common/Util/PrimaryActionButtonStyle.swift +++ b/RiotSwiftUI/Modules/Common/Util/PrimaryActionButtonStyle.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ struct PrimaryActionButtonStyle: ButtonStyle { @Environment(\.theme) private var theme @Environment(\.isEnabled) private var isEnabled - var customColor: Color? = nil + var customColor: Color? private var fontColor: Color { // Always white unless disabled with a dark theme. @@ -65,7 +65,7 @@ struct PrimaryActionButtonStyle_Previews: PreviewProvider { .buttonStyle(PrimaryActionButtonStyle(customColor: .clear)) Button("Red BG") { } - .buttonStyle(PrimaryActionButtonStyle(customColor: .red)) + .buttonStyle(PrimaryActionButtonStyle(customColor: .red)) } .padding() } diff --git a/RiotSwiftUI/Modules/Common/Util/RadioButton.swift b/RiotSwiftUI/Modules/Common/Util/RadioButton.swift index 69e408410..77835c45f 100644 --- a/RiotSwiftUI/Modules/Common/Util/RadioButton.swift +++ b/RiotSwiftUI/Modules/Common/Util/RadioButton.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct RadioButton: View { - // MARK: - Properties var title: String @@ -61,8 +60,8 @@ struct RadioButton_Previews: PreviewProvider { static var buttonGroup: some View { VStack { - RadioButton(title: "A title", selected: false, action: {}) - RadioButton(title: "A title", selected: true, action: {}) + RadioButton(title: "A title", selected: false, action: { }) + RadioButton(title: "A title", selected: true, action: { }) } } } diff --git a/RiotSwiftUI/Modules/Common/Util/ReadableFrameModifier.swift b/RiotSwiftUI/Modules/Common/Util/ReadableFrameModifier.swift index d04f413b2..257431e35 100644 --- a/RiotSwiftUI/Modules/Common/Util/ReadableFrameModifier.swift +++ b/RiotSwiftUI/Modules/Common/Util/ReadableFrameModifier.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ import SwiftUI /// Positions this view within an invisible frame that fills the width of its parent view, /// whilst limiting the width of the content to a readable size (which is customizable). -fileprivate struct ReadableFrameModifier: ViewModifier { +private struct ReadableFrameModifier: ViewModifier { var maxWidth: CGFloat func body(content: Content) -> some View { diff --git a/RiotSwiftUI/Modules/Common/Util/ResponderManager.swift b/RiotSwiftUI/Modules/Common/Util/ResponderManager.swift index 21738bae9..3148c2cf4 100644 --- a/RiotSwiftUI/Modules/Common/Util/ResponderManager.swift +++ b/RiotSwiftUI/Modules/Common/Util/ResponderManager.swift @@ -18,8 +18,7 @@ import UIKit /// `ResponderManager` is used to chain `SwiftUI` text editing views that embed `UIKit` text editing views using `UIViewRepresentable` class ResponderManager { - - private static var tagIndex: Int = 1000 + private static var tagIndex = 1000 private static var registeredResponders = NSMapTable(keyOptions: .strongMemory, valueOptions: .weakMemory) private static var nextIndex: Int { @@ -63,7 +62,7 @@ class ResponderManager { /// Tries to get the focused registered responder and give the focus to it's next responder /// - Returns: `True` if the next responder has been found and is successfully focused. `False` otherwise. static func makeActiveNextResponder() -> Bool { - guard let firstResponder = self.firstResponder else { + guard let firstResponder = firstResponder else { return false } diff --git a/RiotSwiftUI/Modules/Common/Util/RoundedBorderTextEditor.swift b/RiotSwiftUI/Modules/Common/Util/RoundedBorderTextEditor.swift index 38e50ff19..240b9ebba 100644 --- a/RiotSwiftUI/Modules/Common/Util/RoundedBorderTextEditor.swift +++ b/RiotSwiftUI/Modules/Common/Util/RoundedBorderTextEditor.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,17 +17,16 @@ import SwiftUI struct RoundedBorderTextEditor: View { - // MARK: - Properties - var title: String? = nil + var title: String? let placeHolder: String @Binding var text: String - var textMaxHeight: CGFloat? = nil - var error: String? = nil + var textMaxHeight: CGFloat? + var error: String? - var onTextChanged: ((String) -> Void)? = nil - var onEditingChanged: ((Bool) -> Void)? = nil + var onTextChanged: ((String) -> Void)? + var onEditingChanged: ((Bool) -> Void)? @State private var editing = false @@ -62,7 +61,7 @@ struct RoundedBorderTextEditor: View { }) .showClearButton(text: $text) // Found no good solution here. Hidding next button for the moment - // .modifier(NextViewModifier(alignment: .bottomTrailing, isEditing: $editing)) + // .modifier(NextViewModifier(alignment: .bottomTrailing, isEditing: $editing)) .padding(EdgeInsets(top: 2, leading: 6, bottom: 0, trailing: 0)) .onChange(of: text, perform: { newText in onTextChanged?(newText) @@ -82,7 +81,7 @@ struct RoundedBorderTextEditor: View { } .background(RoundedRectangle(cornerRadius: 8).fill(theme.colors.background)) .overlay(RoundedRectangle(cornerRadius: 8) - .stroke(editing ? theme.colors.accent : (error == nil ? theme.colors.quinaryContent : theme.colors.alert), lineWidth: editing || error != nil ? 2 : 1)) + .stroke(editing ? theme.colors.accent : (error == nil ? theme.colors.quinaryContent : theme.colors.alert), lineWidth: editing || error != nil ? 2 : 1)) .frame(height: textMaxHeight) if let error = self.error { Text(error) @@ -101,7 +100,6 @@ struct RoundedBorderTextEditor: View { struct ThemableTextEditor_Previews: PreviewProvider { static var previews: some View { - Group { sampleView.theme(.light).preferredColorScheme(.light) sampleView.theme(.dark).preferredColorScheme(.dark) diff --git a/RiotSwiftUI/Modules/Common/Util/RoundedBorderTextField.swift b/RiotSwiftUI/Modules/Common/Util/RoundedBorderTextField.swift index c0c69a209..7acf765cc 100644 --- a/RiotSwiftUI/Modules/Common/Util/RoundedBorderTextField.swift +++ b/RiotSwiftUI/Modules/Common/Util/RoundedBorderTextField.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,22 +17,21 @@ import SwiftUI struct RoundedBorderTextField: View { - // MARK: - Properties - var title: String? = nil + var title: String? let placeHolder: String @Binding var text: String - var footerText: String? = nil - var isError: Bool = false + var footerText: String? + var isError = false var isFirstResponder = false - var configuration: UIKitTextInputConfiguration = UIKitTextInputConfiguration() + var configuration = UIKitTextInputConfiguration() @State var isSecureTextVisible = false - var onTextChanged: ((String) -> Void)? = nil - var onEditingChanged: ((Bool) -> Void)? = nil - var onCommit: (() -> Void)? = nil + var onTextChanged: ((String) -> Void)? + var onEditingChanged: ((Bool) -> Void)? + var onCommit: (() -> Void)? // MARK: Private @@ -101,7 +100,7 @@ struct RoundedBorderTextField: View { private var borderColor: Color { if isEditing { return theme.colors.accent - } else if footerText != nil && isError { + } else if footerText != nil, isError { return theme.colors.alert } else { return theme.colors.quinaryContent @@ -118,7 +117,6 @@ struct RoundedBorderTextField: View { struct TextFieldWithError_Previews: PreviewProvider { static var previews: some View { - Group { sampleView.theme(.light).preferredColorScheme(.light) sampleView.theme(.dark).preferredColorScheme(.dark) diff --git a/RiotSwiftUI/Modules/Common/Util/RoundedCornerShape.swift b/RiotSwiftUI/Modules/Common/Util/RoundedCornerShape.swift index 0fc77b56e..e34197eec 100644 --- a/RiotSwiftUI/Modules/Common/Util/RoundedCornerShape.swift +++ b/RiotSwiftUI/Modules/Common/Util/RoundedCornerShape.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import Foundation import SwiftUI struct RoundedCornerShape: Shape { - let radius: CGFloat let corners: UIRectCorner diff --git a/RiotSwiftUI/Modules/Common/Util/SafeBindingCollectionEnumerator.swift b/RiotSwiftUI/Modules/Common/Util/SafeBindingCollectionEnumerator.swift index 92d6135cd..f6a16c21f 100644 --- a/RiotSwiftUI/Modules/Common/Util/SafeBindingCollectionEnumerator.swift +++ b/RiotSwiftUI/Modules/Common/Util/SafeBindingCollectionEnumerator.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,6 @@ import SwiftUI Replace with Swift 5.5 bindings enumerator later. */ struct SafeBindingCollectionEnumerator: View { - typealias BoundElement = Binding private let binding: BoundElement private let content: (BoundElement) -> C diff --git a/RiotSwiftUI/Modules/Common/Util/ScreenTrackerViewModifier.swift b/RiotSwiftUI/Modules/Common/Util/ScreenTrackerViewModifier.swift index b8eaf74d2..fe5ec5bb5 100644 --- a/RiotSwiftUI/Modules/Common/Util/ScreenTrackerViewModifier.swift +++ b/RiotSwiftUI/Modules/Common/Util/ScreenTrackerViewModifier.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,6 +33,6 @@ struct ScreenTrackerViewModifier: ViewModifier { extension View { func track(screen: AnalyticsScreen) -> some View { - return self.modifier(ScreenTrackerViewModifier(screen: screen)) + modifier(ScreenTrackerViewModifier(screen: screen)) } } diff --git a/RiotSwiftUI/Modules/Common/Util/SearchBar.swift b/RiotSwiftUI/Modules/Common/Util/SearchBar.swift index 4edaa2e5c..63b3863b9 100644 --- a/RiotSwiftUI/Modules/Common/Util/SearchBar.swift +++ b/RiotSwiftUI/Modules/Common/Util/SearchBar.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct SearchBar: View { - // MARK: - Properties var placeholder: String @@ -49,7 +48,7 @@ struct SearchBar: View { .foregroundColor(theme.colors.quarterlyContent) .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) - if isEditing && !text.isEmpty { + if isEditing, !text.isEmpty { Button(action: { self.text = "" }) { diff --git a/RiotSwiftUI/Modules/Common/Util/SecondaryActionButtonStyle.swift b/RiotSwiftUI/Modules/Common/Util/SecondaryActionButtonStyle.swift index 8f0eb6aac..917ad1997 100644 --- a/RiotSwiftUI/Modules/Common/Util/SecondaryActionButtonStyle.swift +++ b/RiotSwiftUI/Modules/Common/Util/SecondaryActionButtonStyle.swift @@ -20,7 +20,7 @@ struct SecondaryActionButtonStyle: ButtonStyle { @Environment(\.theme) private var theme @Environment(\.isEnabled) private var isEnabled - var customColor: Color? = nil + var customColor: Color? func makeBody(configuration: Self.Configuration) -> some View { configuration.label @@ -29,8 +29,8 @@ struct SecondaryActionButtonStyle: ButtonStyle { .foregroundColor(customColor ?? theme.colors.accent) .font(theme.fonts.body) .background(RoundedRectangle(cornerRadius: 8) - .strokeBorder() - .foregroundColor(customColor ?? theme.colors.accent)) + .strokeBorder() + .foregroundColor(customColor ?? theme.colors.accent)) .opacity(opacity(when: configuration.isPressed)) } @@ -62,7 +62,7 @@ struct SecondaryActionButtonStyle_Previews: PreviewProvider { .disabled(true) Button("Red BG") { } - .buttonStyle(SecondaryActionButtonStyle(customColor: .red)) + .buttonStyle(SecondaryActionButtonStyle(customColor: .red)) Button { } label: { Text("Custom") diff --git a/RiotSwiftUI/Modules/Common/Util/StyledText.swift b/RiotSwiftUI/Modules/Common/Util/StyledText.swift index 1c4b0a74f..0ed73d4a9 100644 --- a/RiotSwiftUI/Modules/Common/Util/StyledText.swift +++ b/RiotSwiftUI/Modules/Common/Util/StyledText.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,14 +14,13 @@ // limitations under the License. // -import SwiftUI import DesignKit +import SwiftUI @available(iOS, introduced: 14.0, deprecated: 15.0, message: "Use Text with an AttributedString instead.") /// A `Text` view that renders attributed strings with their `.font` and `.foregroundColor` attributes. /// This view is a workaround for iOS 13/14 not supporting `AttributedString`. struct StyledText: View { - // MARK: - Properties // MARK: Private @@ -31,8 +30,8 @@ struct StyledText: View { /// A string with a bold property. private struct StringComponent { let string: String - var font: Font? = nil - var color: Color? = nil + var font: Font? + var color: Color? } /// Internal representation of the string as composable parts. @@ -47,7 +46,7 @@ struct StyledText: View { let range = NSRange(location: 0, length: attributedString.length) let string = attributedString.string as NSString - attributedString.enumerateAttributes(in: range, options: []) { attributes, range, stop in + attributedString.enumerateAttributes(in: range, options: []) { attributes, range, _ in let font = attributes[.font] as? UIFont let color = attributes[.foregroundColor] as? UIColor @@ -66,7 +65,7 @@ struct StyledText: View { /// Creates a `StyledText` using a plain string. /// - Parameter string: The plain string to display init(_ string: String) { - self.components = [StringComponent(string: string, font: nil)] + components = [StringComponent(string: string, font: nil)] } // MARK: - Views @@ -80,7 +79,6 @@ struct StyledText: View { } } - struct StyledText_Previews: PreviewProvider { static func prettyText() -> NSAttributedString { let string = NSMutableAttributedString(string: "T", attributes: [ diff --git a/RiotSwiftUI/Modules/Common/Util/ThemableButton.swift b/RiotSwiftUI/Modules/Common/Util/ThemableButton.swift index 61a066c38..61dd9a84e 100644 --- a/RiotSwiftUI/Modules/Common/Util/ThemableButton.swift +++ b/RiotSwiftUI/Modules/Common/Util/ThemableButton.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,11 +17,9 @@ import SwiftUI struct ThemableButton: View { - // MARK: - Style private struct Style: ButtonStyle { - func makeBody(configuration: Configuration) -> some View { configuration.label .scaleEffect(configuration.isPressed ? 0.97 : 1) @@ -67,12 +65,12 @@ struct ThemableButton_Previews: PreviewProvider { static var previews: some View { Group { VStack(alignment: .center, spacing: 20) { - ThemableButton(icon: Asset.Images.spaceTypeIcon.image, title: "A title", action: {}).theme(.light).preferredColorScheme(.light) - ThemableButton(icon: nil, title: "A title", action: {}).theme(.light).preferredColorScheme(.light) + ThemableButton(icon: Asset.Images.spaceTypeIcon.image, title: "A title", action: { }).theme(.light).preferredColorScheme(.light) + ThemableButton(icon: nil, title: "A title", action: { }).theme(.light).preferredColorScheme(.light) } VStack(alignment: .center, spacing: 20) { - ThemableButton(icon: Asset.Images.spaceTypeIcon.image, title: "A title", action: {}).theme(.dark).preferredColorScheme(.dark) - ThemableButton(icon: nil, title: "A title", action: {}).theme(.dark).preferredColorScheme(.dark) + ThemableButton(icon: Asset.Images.spaceTypeIcon.image, title: "A title", action: { }).theme(.dark).preferredColorScheme(.dark) + ThemableButton(icon: nil, title: "A title", action: { }).theme(.dark).preferredColorScheme(.dark) } } .padding() diff --git a/RiotSwiftUI/Modules/Common/Util/ThemableNavigationBar.swift b/RiotSwiftUI/Modules/Common/Util/ThemableNavigationBar.swift index 2c6fc3727..3f7bf833e 100644 --- a/RiotSwiftUI/Modules/Common/Util/ThemableNavigationBar.swift +++ b/RiotSwiftUI/Modules/Common/Util/ThemableNavigationBar.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct ThemableNavigationBar: View { - // MARK: - Style // MARK: - Properties @@ -36,8 +35,7 @@ struct ThemableNavigationBar: View { @ViewBuilder var body: some View { HStack { - Button(action: {backAction()}) - { + Button(action: { backAction() }) { Image(uiImage: Asset.Images.spacesModalBack.image) .renderingMode(.template) .foregroundColor(theme.colors.secondaryContent) @@ -49,8 +47,7 @@ struct ThemableNavigationBar: View { .foregroundColor(theme.colors.primaryContent) } Spacer() - Button(action: {closeAction()}) - { + Button(action: { closeAction() }) { Image(uiImage: Asset.Images.spacesModalClose.image) .renderingMode(.template) .foregroundColor(theme.colors.secondaryContent) @@ -68,16 +65,16 @@ struct NavigationBar_Previews: PreviewProvider { static var previews: some View { Group { VStack { - ThemableNavigationBar(title: nil, showBackButton: true, backAction: {}, closeAction: {}) - ThemableNavigationBar(title: "Some Title", showBackButton: true, backAction: {}, closeAction: {}) - ThemableNavigationBar(title: nil, showBackButton: false, backAction: {}, closeAction: {}) - ThemableNavigationBar(title: "Some Title", showBackButton: false, backAction: {}, closeAction: {}) + ThemableNavigationBar(title: nil, showBackButton: true, backAction: { }, closeAction: { }) + ThemableNavigationBar(title: "Some Title", showBackButton: true, backAction: { }, closeAction: { }) + ThemableNavigationBar(title: nil, showBackButton: false, backAction: { }, closeAction: { }) + ThemableNavigationBar(title: "Some Title", showBackButton: false, backAction: { }, closeAction: { }) } VStack { - ThemableNavigationBar(title: nil, showBackButton: true, backAction: {}, closeAction: {}).theme(.dark) - ThemableNavigationBar(title: "Some Title", showBackButton: true, backAction: {}, closeAction: {}).theme(.dark) - ThemableNavigationBar(title: nil, showBackButton: false, backAction: {}, closeAction: {}).theme(.dark) - ThemableNavigationBar(title: "Some Title", showBackButton: false, backAction: {}, closeAction: {}).theme(.dark) + ThemableNavigationBar(title: nil, showBackButton: true, backAction: { }, closeAction: { }).theme(.dark) + ThemableNavigationBar(title: "Some Title", showBackButton: true, backAction: { }, closeAction: { }).theme(.dark) + ThemableNavigationBar(title: nil, showBackButton: false, backAction: { }, closeAction: { }).theme(.dark) + ThemableNavigationBar(title: "Some Title", showBackButton: false, backAction: { }, closeAction: { }).theme(.dark) }.preferredColorScheme(.dark) } .padding() diff --git a/RiotSwiftUI/Modules/Common/Util/ThemableTextEditor.swift b/RiotSwiftUI/Modules/Common/Util/ThemableTextEditor.swift index d9f0d1ca7..bdc529822 100644 --- a/RiotSwiftUI/Modules/Common/Util/ThemableTextEditor.swift +++ b/RiotSwiftUI/Modules/Common/Util/ThemableTextEditor.swift @@ -16,20 +16,18 @@ import SwiftUI - struct ThemableTextEditor: UIViewRepresentable { - // MARK: Properties @Binding var text: String - @State var configuration: UIKitTextInputConfiguration = UIKitTextInputConfiguration() + @State var configuration = UIKitTextInputConfiguration() var onEditingChanged: ((_ edit: Bool) -> Void)? // MARK: Private @Environment(\.theme) private var theme: ThemeSwiftUI - private let textView: UITextView = UITextView() + private let textView = UITextView() private let internalParams = InternalParams() // MARK: Setup @@ -37,8 +35,8 @@ struct ThemableTextEditor: UIViewRepresentable { init(text: Binding, configuration: UIKitTextInputConfiguration = UIKitTextInputConfiguration(), onEditingChanged: ((_ edit: Bool) -> Void)? = nil) { - self._text = text - self._configuration = State(initialValue: configuration) + _text = text + _configuration = State(initialValue: configuration) self.onEditingChanged = onEditingChanged ResponderManager.register(view: textView) @@ -63,8 +61,8 @@ struct ThemableTextEditor: UIViewRepresentable { uiView.textColor = UIColor(theme.colors.primaryContent) uiView.tintColor = UIColor(theme.colors.accent) - if uiView.text != self.text { - uiView.text = self.text + if uiView.text != text { + uiView.text = text } uiView.keyboardType = configuration.keyboardType @@ -81,7 +79,7 @@ struct ThemableTextEditor: UIViewRepresentable { // MARK: - Private private func replaceText(with newText: String) { - self.text = newText + text = newText } private class InternalParams { @@ -91,7 +89,7 @@ struct ThemableTextEditor: UIViewRepresentable { // MARK: - Coordinator func makeCoordinator() -> Coordinator { - return Coordinator(self) + Coordinator(self) } class Coordinator: NSObject, UITextViewDelegate { diff --git a/RiotSwiftUI/Modules/Common/Util/ThemableTextField.swift b/RiotSwiftUI/Modules/Common/Util/ThemableTextField.swift index fa31d1f02..a179d36ab 100644 --- a/RiotSwiftUI/Modules/Common/Util/ThemableTextField.swift +++ b/RiotSwiftUI/Modules/Common/Util/ThemableTextField.swift @@ -19,18 +19,17 @@ import SwiftUI struct UIKitTextInputConfiguration { var keyboardType: UIKeyboardType = .default var returnKeyType: UIReturnKeyType = .default - var isSecureTextEntry: Bool = false + var isSecureTextEntry = false var autocapitalizationType: UITextAutocapitalizationType = .sentences var autocorrectionType: UITextAutocorrectionType = .default } struct ThemableTextField: UIViewRepresentable { - // MARK: Properties @State var placeholder: String? @Binding var text: String - @State var configuration: UIKitTextInputConfiguration = UIKitTextInputConfiguration() + @State var configuration = UIKitTextInputConfiguration() @Binding var isSecureTextVisible: Bool var onEditingChanged: ((_ edit: Bool) -> Void)? var onCommit: (() -> Void)? @@ -39,7 +38,7 @@ struct ThemableTextField: UIViewRepresentable { @Environment(\.theme) private var theme: ThemeSwiftUI - private let textField: UITextField = UITextField() + private let textField = UITextField() private let internalParams = InternalParams() // MARK: Setup @@ -50,10 +49,10 @@ struct ThemableTextField: UIViewRepresentable { isSecureTextVisible: Binding = .constant(false), onEditingChanged: ((_ edit: Bool) -> Void)? = nil, onCommit: (() -> Void)? = nil) { - self._text = text - self._placeholder = State(initialValue: placeholder) - self._configuration = State(initialValue: configuration) - self._isSecureTextVisible = isSecureTextVisible + _text = text + _placeholder = State(initialValue: placeholder) + _configuration = State(initialValue: configuration) + _isSecureTextVisible = isSecureTextVisible self.onEditingChanged = onEditingChanged self.onCommit = onCommit @@ -84,8 +83,8 @@ struct ThemableTextField: UIViewRepresentable { uiView.textColor = UIColor(theme.colors.primaryContent) uiView.tintColor = UIColor(theme.colors.accent) - if uiView.text != self.text { - uiView.text = self.text + if uiView.text != text { + uiView.text = text } uiView.placeholder = placeholder @@ -103,17 +102,16 @@ struct ThemableTextField: UIViewRepresentable { // MARK: - Private private func replaceText(with newText: String) { - self.text = newText + text = newText } // MARK: - Coordinator func makeCoordinator() -> Coordinator { - return Coordinator(self) + Coordinator(self) } class Coordinator: NSObject, UITextFieldDelegate { - var parent: ThemableTextField init(_ parent: ThemableTextField) { @@ -146,14 +144,13 @@ struct ThemableTextField: UIViewRepresentable { private class InternalParams { var isFirstResponder = false } - } // MARK: - modifiers extension ThemableTextField { func makeFirstResponder() -> ThemableTextField { - return makeFirstResponder(true) + makeFirstResponder(true) } func makeFirstResponder(_ isFirstResponder: Bool) -> ThemableTextField { @@ -167,7 +164,7 @@ extension ThemableTextField { /// - alignment: The vertical alignment of the button in the text field. Default to `center` @ViewBuilder func addButton(_ show: Bool, alignment: VerticalAlignment = .center) -> some View { - if show && configuration.isSecureTextEntry { + if show, configuration.isSecureTextEntry { modifier(PasswordButtonModifier(text: text, isSecureTextVisible: $isSecureTextVisible, alignment: alignment)) diff --git a/RiotSwiftUI/Modules/Common/Util/View+Riot.swift b/RiotSwiftUI/Modules/Common/Util/View+Riot.swift index d126ec8d0..89aae532c 100644 --- a/RiotSwiftUI/Modules/Common/Util/View+Riot.swift +++ b/RiotSwiftUI/Modules/Common/Util/View+Riot.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ import SwiftUI extension View { @ViewBuilder func isHidden(_ isHidden: Bool) -> some View { if isHidden { - self.hidden() + hidden() } else { self } diff --git a/RiotSwiftUI/Modules/Common/Util/WaitOverlay.swift b/RiotSwiftUI/Modules/Common/Util/WaitOverlay.swift index 60f7315bb..db88cc486 100644 --- a/RiotSwiftUI/Modules/Common/Util/WaitOverlay.swift +++ b/RiotSwiftUI/Modules/Common/Util/WaitOverlay.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import SwiftUI /// A modifier for showing the wait overlay view over a view. struct WaitOverlayModifier: ViewModifier { - var allowUserInteraction: Bool var show: Bool var message: String? @@ -27,15 +26,16 @@ struct WaitOverlayModifier: ViewModifier { func body(content: Content) -> some View { content .modifier(WaitOverlay( - allowUserInteraction: allowUserInteraction, - message: message, - isLoading: show)) + allowUserInteraction: allowUserInteraction, + message: message, + isLoading: show + )) } } extension View { func waitOverlay(show: Bool, message: String? = nil, allowUserInteraction: Bool = true) -> some View { - self.modifier(WaitOverlayModifier(allowUserInteraction: allowUserInteraction, show: show, message: message)) + modifier(WaitOverlayModifier(allowUserInteraction: allowUserInteraction, show: show, message: message)) } } @@ -44,7 +44,7 @@ struct WaitOverlay: ViewModifier { // MARK: - Properties var alignment: Alignment = .center - var allowUserInteraction: Bool = true + var allowUserInteraction = true var message: String? var isLoading: Bool @@ -66,8 +66,7 @@ struct WaitOverlay: ViewModifier { // MARK: - Public - public func body(content: Content) -> some View - { + public func body(content: Content) -> some View { ZStack { content if isLoading { @@ -89,7 +88,7 @@ struct WaitOverlay: ViewModifier { } .padding(12) .background(RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(theme.colors.navigation.opacity(0.9))) + .fill(theme.colors.navigation.opacity(0.9))) } .edgesIgnoringSafeArea(.all) .transition(.opacity) @@ -103,24 +102,24 @@ struct WaitOverlay_Previews: PreviewProvider { static var previews: some View { Group { VStack { - ThemableNavigationBar(title: nil, showBackButton: true, backAction: {}, closeAction: {}) - ThemableNavigationBar(title: "Some Title", showBackButton: true, backAction: {}, closeAction: {}) - ThemableNavigationBar(title: nil, showBackButton: false, backAction: {}, closeAction: {}) - ThemableNavigationBar(title: "Some Title", showBackButton: false, backAction: {}, closeAction: {}) + ThemableNavigationBar(title: nil, showBackButton: true, backAction: { }, closeAction: { }) + ThemableNavigationBar(title: "Some Title", showBackButton: true, backAction: { }, closeAction: { }) + ThemableNavigationBar(title: nil, showBackButton: false, backAction: { }, closeAction: { }) + ThemableNavigationBar(title: "Some Title", showBackButton: false, backAction: { }, closeAction: { }) } .modifier(WaitOverlay(isLoading: true)) VStack { - ThemableNavigationBar(title: nil, showBackButton: true, backAction: {}, closeAction: {}) - ThemableNavigationBar(title: "Some Title", showBackButton: true, backAction: {}, closeAction: {}) - ThemableNavigationBar(title: nil, showBackButton: false, backAction: {}, closeAction: {}) - ThemableNavigationBar(title: "Some Title", showBackButton: false, backAction: {}, closeAction: {}) + ThemableNavigationBar(title: nil, showBackButton: true, backAction: { }, closeAction: { }) + ThemableNavigationBar(title: "Some Title", showBackButton: true, backAction: { }, closeAction: { }) + ThemableNavigationBar(title: nil, showBackButton: false, backAction: { }, closeAction: { }) + ThemableNavigationBar(title: "Some Title", showBackButton: false, backAction: { }, closeAction: { }) } - .modifier(WaitOverlay(alignment:.topLeading, isLoading: true)) + .modifier(WaitOverlay(alignment: .topLeading, isLoading: true)) VStack { - ThemableNavigationBar(title: nil, showBackButton: true, backAction: {}, closeAction: {}).theme(.dark) - ThemableNavigationBar(title: "Some Title", showBackButton: true, backAction: {}, closeAction: {}).theme(.dark) - ThemableNavigationBar(title: nil, showBackButton: false, backAction: {}, closeAction: {}).theme(.dark) - ThemableNavigationBar(title: "Some Title", showBackButton: false, backAction: {}, closeAction: {}).theme(.dark) + ThemableNavigationBar(title: nil, showBackButton: true, backAction: { }, closeAction: { }).theme(.dark) + ThemableNavigationBar(title: "Some Title", showBackButton: true, backAction: { }, closeAction: { }).theme(.dark) + ThemableNavigationBar(title: nil, showBackButton: false, backAction: { }, closeAction: { }).theme(.dark) + ThemableNavigationBar(title: "Some Title", showBackButton: false, backAction: { }, closeAction: { }).theme(.dark) } .modifier(WaitOverlay(isLoading: true)).theme(.dark) diff --git a/RiotSwiftUI/Modules/Common/ViewFrameReader/FramePreferenceKey.swift b/RiotSwiftUI/Modules/Common/ViewFrameReader/FramePreferenceKey.swift index 1eb7e97dd..63eb8df9d 100644 --- a/RiotSwiftUI/Modules/Common/ViewFrameReader/FramePreferenceKey.swift +++ b/RiotSwiftUI/Modules/Common/ViewFrameReader/FramePreferenceKey.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Common/ViewFrameReader/ViewFrameReader.swift b/RiotSwiftUI/Modules/Common/ViewFrameReader/ViewFrameReader.swift index 834c018a7..1bb6c50de 100644 --- a/RiotSwiftUI/Modules/Common/ViewFrameReader/ViewFrameReader.swift +++ b/RiotSwiftUI/Modules/Common/ViewFrameReader/ViewFrameReader.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Common/ViewModel/BindableState.swift b/RiotSwiftUI/Modules/Common/ViewModel/BindableState.swift index 79e658af4..8e3d9ce79 100644 --- a/RiotSwiftUI/Modules/Common/ViewModel/BindableState.swift +++ b/RiotSwiftUI/Modules/Common/ViewModel/BindableState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Common/ViewModel/StateStoreViewModel.swift b/RiotSwiftUI/Modules/Common/ViewModel/StateStoreViewModel.swift index 63c1ae7ae..30641c7f9 100644 --- a/RiotSwiftUI/Modules/Common/ViewModel/StateStoreViewModel.swift +++ b/RiotSwiftUI/Modules/Common/ViewModel/StateStoreViewModel.swift @@ -14,12 +14,11 @@ // limitations under the License. // -import Foundation import Combine - import Foundation -import Combine +import Combine +import Foundation /// A constrained and concise interface for interacting with the ViewModel. /// @@ -34,7 +33,7 @@ import Combine /// It provides a nice layer of consistency and also safety. As we are not passing the `ViewModel` to the view directly, shortcuts/hacks /// can't be made into the `ViewModel`. @dynamicMemberLookup -class ViewModelContext: ObservableObject { +class ViewModelContext: ObservableObject { // MARK: - Properties // MARK: Private @@ -75,7 +74,6 @@ class ViewModelContext: ObservableObject { /// If we decide to add more features to our state management (like doing state processing off the main thread) /// we can do it in this centralised place. class StateStoreViewModel { - typealias Context = ViewModelContext // MARK: - Properties @@ -98,8 +96,8 @@ class StateStoreViewModel { // MARK: Setup init(initialViewState: State) { - self.context = Context(initialViewState: initialViewState) - self.context.viewActions.sink { [weak self] action in + context = Context(initialViewState: initialViewState) + context.viewActions.sink { [weak self] action in guard let self = self else { return } self.process(viewAction: action) } @@ -131,12 +129,12 @@ class StateStoreViewModel { /// - state: The `inout` state to be modified, /// - action: The action that defines which state modification should take place. class func reducer(state: inout State, action: StateAction) { - //Default implementation, -no-op + // Default implementation, -no-op } /// Override to handles incoming `ViewAction`s from the `ViewModel`. /// - Parameter viewAction: The `ViewAction` to be processed in `ViewModel` implementation. func process(viewAction: ViewAction) { - //Default implementation, -no-op + // Default implementation, -no-op } } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/Coordinator/LiveLocationLabPromotionCoordinator.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/Coordinator/LiveLocationLabPromotionCoordinator.swift index fbc5c6c9f..4bbb7c8bf 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/Coordinator/LiveLocationLabPromotionCoordinator.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/Coordinator/LiveLocationLabPromotionCoordinator.swift @@ -17,7 +17,6 @@ import SwiftUI final class LiveLocationLabPromotionCoordinator: NSObject, Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -50,7 +49,7 @@ final class LiveLocationLabPromotionCoordinator: NSObject, Coordinator, Presenta func start() { MXLog.debug("[LiveLocationLabPromotionCoordinator] did start.") - self.liveLocationLabPromotionViewModel.completion = { [weak self] enableLiveLocation in + liveLocationLabPromotionViewModel.completion = { [weak self] enableLiveLocation in guard let self = self else { return } RiotSettings.shared.enableLiveLocationSharing = enableLiveLocation @@ -62,15 +61,14 @@ final class LiveLocationLabPromotionCoordinator: NSObject, Coordinator, Presenta } func toPresentable() -> UIViewController { - return self.liveLocationLabPromotionHostingController + liveLocationLabPromotionHostingController } } // MARK: - UIAdaptivePresentationControllerDelegate extension LiveLocationLabPromotionCoordinator: UIAdaptivePresentationControllerDelegate { - func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { - self.completion?(RiotSettings.shared.enableLiveLocationSharing) + completion?(RiotSettings.shared.enableLiveLocationSharing) } } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionModels.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionModels.swift index 266b2d01c..7f5f19429 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionModels.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import Foundation // MARK: View struct LiveLocationLabPromotionViewState: BindableState { - var bindings: LiveLocationLabPromotionBindings } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionViewModel.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionViewModel.swift index eb4ea189c..6a6b48a7d 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionViewModel.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionViewModel.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,11 +17,10 @@ import SwiftUI typealias LiveLocationLabPromotionViewModelType = StateStoreViewModel + Never, + LiveLocationLabPromotionViewAction> class LiveLocationLabPromotionViewModel: LiveLocationLabPromotionViewModelType, LiveLocationLabPromotionViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -42,7 +41,7 @@ class LiveLocationLabPromotionViewModel: LiveLocationLabPromotionViewModelType, override func process(viewAction: LiveLocationLabPromotionViewAction) { switch viewAction { case .complete: - completion?(self.state.bindings.enableLabFlag) + completion?(state.bindings.enableLabFlag) } } } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionViewModelProtocol.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionViewModelProtocol.swift index a8d741751..45d75cd1c 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol LiveLocationLabPromotionViewModelProtocol { - /// Closure called when screen completes. Indicates true if the lab flag has been enabled. var completion: ((Bool) -> Void)? { get set } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/MockLiveLocationLabPromotionScreenState.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/MockLiveLocationLabPromotionScreenState.swift index f062bfe5f..c49c9e1de 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/MockLiveLocationLabPromotionScreenState.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/MockLiveLocationLabPromotionScreenState.swift @@ -31,7 +31,7 @@ enum MockLiveLocationLabPromotionScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel = LiveLocationLabPromotionViewModel() // can simulate service and viewModel actions here if needs be. @@ -39,6 +39,7 @@ enum MockLiveLocationLabPromotionScreenState: MockScreenState, CaseIterable { return ( [self, viewModel], AnyView(LiveLocationLabPromotionView(viewModel: viewModel.context) - )) + ) + ) } } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/Test/UI/LiveLocationLabPromotionUITests.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/Test/UI/LiveLocationLabPromotionUITests.swift index 4b71228fc..4a8af4a5b 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/Test/UI/LiveLocationLabPromotionUITests.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/Test/UI/LiveLocationLabPromotionUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class LiveLocationLabPromotionUITests: MockScreenTestCase { // Nothing to test as the view is completely static diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/Test/Unit/LiveLocationLabPromotionViewModelTests.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/Test/Unit/LiveLocationLabPromotionViewModelTests.swift index 284b27694..9dc77e699 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/Test/Unit/LiveLocationLabPromotionViewModelTests.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/Test/Unit/LiveLocationLabPromotionViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/View/LiveLocationLabPromotionView.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/View/LiveLocationLabPromotionView.swift index 8ca11dd23..c92c0e2df 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/View/LiveLocationLabPromotionView.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/View/LiveLocationLabPromotionView.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct LiveLocationLabPromotionView: View { - // MARK: - Properties // MARK: Private @@ -49,7 +48,6 @@ struct LiveLocationLabPromotionView: View { .padding(.top, 1) Toggle(isOn: $viewModel.enableLabFlag) { - Text(VectorL10n.locationSharingLiveLabPromotionActivation) .font(theme.fonts.body) .foregroundColor(theme.colors.primaryContent) diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Coordinator/LiveLocationSharingViewerCoordinator.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Coordinator/LiveLocationSharingViewerCoordinator.swift index 19be80eec..fc5c6b87a 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Coordinator/LiveLocationSharingViewerCoordinator.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Coordinator/LiveLocationSharingViewerCoordinator.swift @@ -23,7 +23,6 @@ struct LiveLocationSharingViewerCoordinatorParameters { } final class LiveLocationSharingViewerCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -51,7 +50,8 @@ final class LiveLocationSharingViewerCoordinator: Coordinator, Presentable { let viewModel = LiveLocationSharingViewerViewModel( mapStyleURL: parameters.session.vc_homeserverConfiguration().tileServer.mapStyleURL, - service: service) + service: service + ) let view = LiveLocationSharingViewer(viewModel: viewModel.context) .addDependency(AvatarService.instantiate(mediaManager: parameters.session.mediaManager)) liveLocationSharingViewerViewModel = viewModel @@ -61,6 +61,7 @@ final class LiveLocationSharingViewerCoordinator: Coordinator, Presentable { } // MARK: - Public + func start() { MXLog.debug("[LiveLocationSharingViewerCoordinator] did start.") liveLocationSharingViewerViewModel.completion = { [weak self] result in @@ -74,7 +75,7 @@ final class LiveLocationSharingViewerCoordinator: Coordinator, Presentable { } } - let viewController: UIViewController = self.liveLocationSharingViewerHostingController + let viewController: UIViewController = liveLocationSharingViewerHostingController if navigationRouter.modules.count > 1 { navigationRouter.push(viewController, animated: true, popCompletion: nil) @@ -84,14 +85,13 @@ final class LiveLocationSharingViewerCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return navigationRouter.toPresentable() + navigationRouter.toPresentable() .vc_setModalFullScreen(true) // Set fullscreen as DSBottomSheet is not working with modal pan gesture recognizer } func presentLocationActivityController(with coordinate: CLLocationCoordinate2D) { - let shareActivityController = shareLocationActivityControllerBuilder.build(with: coordinate) - self.liveLocationSharingViewerHostingController.present(shareActivityController, animated: true) + liveLocationSharingViewerHostingController.present(shareActivityController, animated: true) } } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerModels.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerModels.swift index 3cf3271cc..5c4f8a841 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerModels.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,15 +14,15 @@ // limitations under the License. // -import Foundation import Combine import CoreLocation +import Foundation // MARK: - Coordinator // MARK: View model -enum LiveLocationSharingViewerViewModelResult { +enum LiveLocationSharingViewerViewModelResult { case done case share(_ coordinate: CLLocationCoordinate2D) } @@ -30,7 +30,6 @@ enum LiveLocationSharingViewerViewModelResult { // MARK: View struct LiveLocationSharingViewerViewState: BindableState { - /// Map style URL let mapStyleURL: URL @@ -43,7 +42,7 @@ struct LiveLocationSharingViewerViewState: BindableState { /// Live location list items var listItemsViewData: [LiveLocationListItemViewData] - var showLoadingIndicator: Bool = false + var showLoadingIndicator = false var shareButtonEnabled: Bool { !showLoadingIndicator @@ -51,14 +50,14 @@ struct LiveLocationSharingViewerViewState: BindableState { /// True to indicate that everybody stopped to share live location sharing in the room var isAllLocationSharingEnded: Bool { - return listItemsViewData.isEmpty + listItemsViewData.isEmpty } var isBottomSheetVisible: Bool { - return isAllLocationSharingEnded == false + isAllLocationSharingEnded == false } - var showMapLoadingError: Bool = false + var showMapLoadingError = false let errorSubject = PassthroughSubject() diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift index 9a20870d6..9037b3af6 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift @@ -14,15 +14,14 @@ // limitations under the License. // -import SwiftUI import Combine import Mapbox +import SwiftUI typealias LiveLocationSharingViewerViewModelType = StateStoreViewModel + Never, + LiveLocationSharingViewerViewAction> class LiveLocationSharingViewerViewModel: LiveLocationSharingViewerViewModelType, LiveLocationSharingViewerViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -44,7 +43,6 @@ class LiveLocationSharingViewerViewModel: LiveLocationSharingViewerViewModelType // MARK: - Setup init(mapStyleURL: URL, service: LiveLocationSharingViewerServiceProtocol) { - let viewState = LiveLocationSharingViewerViewState(mapStyleURL: mapStyleURL, annotations: [], highlightedAnnotation: nil, listItemsViewData: []) liveLocationSharingViewerService = service @@ -57,8 +55,8 @@ class LiveLocationSharingViewerViewModel: LiveLocationSharingViewerViewModelType self.processError(error) }.store(in: &cancellables) - self.setupLocationSharingService() - self.setupScreenUpdateTimer() + setupLocationSharingService() + setupScreenUpdateTimer() } // MARK: - Public @@ -70,7 +68,7 @@ class LiveLocationSharingViewerViewModel: LiveLocationSharingViewerViewModelType case .stopSharing: stopUserLocationSharing() case .tapListItem(let userId): - self.highlighAnnotation(with: userId) + highlighAnnotation(with: userId) case .share(let userLocationAnnotation): completion?(.share(userLocationAnnotation.coordinate)) case .mapCreditsDidTap: @@ -81,20 +79,20 @@ class LiveLocationSharingViewerViewModel: LiveLocationSharingViewerViewModelType // MARK: - Private private func setupLocationSharingService() { - self.updateUsersLiveLocation(highlightFirstLocation: true) + updateUsersLiveLocation(highlightFirstLocation: true) liveLocationSharingViewerService.didUpdateUsersLiveLocation = { [weak self] liveLocations in self?.update(with: liveLocations, highlightFirstLocation: false) } - self.liveLocationSharingViewerService.startListeningLiveLocationUpdates() + liveLocationSharingViewerService.startListeningLiveLocationUpdates() } private func updateUsersLiveLocation(highlightFirstLocation: Bool) { - self.update(with: liveLocationSharingViewerService.usersLiveLocation, highlightFirstLocation: highlightFirstLocation) + update(with: liveLocationSharingViewerService.usersLiveLocation, highlightFirstLocation: highlightFirstLocation) } private func setupScreenUpdateTimer() { - self.screenUpdateTimer = Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { [weak self] timer in + screenUpdateTimer = Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { [weak self] _ in self?.updateUsersLiveLocation(highlightFirstLocation: false) } @@ -113,7 +111,7 @@ class LiveLocationSharingViewerViewModel: LiveLocationSharingViewerViewModelType switch error { case .invalidLocationAuthorization: - if let applicationSettingsURL = URL(string:UIApplication.openSettingsURLString) { + if let applicationSettingsURL = URL(string: UIApplication.openSettingsURLString) { UIApplication.shared.open(applicationSettingsURL) } else { self?.completion?(.done) @@ -127,21 +125,19 @@ class LiveLocationSharingViewerViewModel: LiveLocationSharingViewerViewModelType } private func userLocationAnnotations(from usersLiveLocation: [UserLiveLocation]) -> [UserLocationAnnotation] { - - return usersLiveLocation.map { userLiveLocation in - return UserLocationAnnotation(avatarData: userLiveLocation.avatarData, coordinate: userLiveLocation.coordinate) + usersLiveLocation.map { userLiveLocation in + UserLocationAnnotation(avatarData: userLiveLocation.avatarData, coordinate: userLiveLocation.coordinate) } } private func currentUserLocationAnnotation(from annotations: [UserLocationAnnotation]) -> UserLocationAnnotation? { annotations.first { annotation in - return liveLocationSharingViewerService.isCurrentUserId(annotation.userId) + liveLocationSharingViewerService.isCurrentUserId(annotation.userId) } } private func getHighlightedAnnotation(from annotations: [UserLocationAnnotation]) -> UserLocationAnnotation? { - - if let userAnnotation = self.currentUserLocationAnnotation(from: annotations) { + if let userAnnotation = currentUserLocationAnnotation(from: annotations) { return userAnnotation } else { return annotations.first @@ -149,25 +145,22 @@ class LiveLocationSharingViewerViewModel: LiveLocationSharingViewerViewModelType } private func listItemsViewData(from usersLiveLocation: [UserLiveLocation]) -> [LiveLocationListItemViewData] { - var listItemsViewData: [LiveLocationListItemViewData] = [] let sortedUsersLiveLocation = usersLiveLocation.sorted { userLiveLocation1, userLiveLocation2 in - return userLiveLocation1.displayName > userLiveLocation2.displayName + userLiveLocation1.displayName > userLiveLocation2.displayName } - listItemsViewData = sortedUsersLiveLocation.map({ userLiveLocation in - return self.listItemViewData(from: userLiveLocation) - }) - + listItemsViewData = sortedUsersLiveLocation.map { userLiveLocation in + self.listItemViewData(from: userLiveLocation) + } let currentUserIndex = listItemsViewData.firstIndex { viewData in - return viewData.isCurrentUser + viewData.isCurrentUser } // Move current user as first item if let currentUserIndex = currentUserIndex { - let currentUserViewData = listItemsViewData[currentUserIndex] listItemsViewData.remove(at: currentUserIndex) listItemsViewData.insert(currentUserViewData, at: 0) @@ -177,41 +170,39 @@ class LiveLocationSharingViewerViewModel: LiveLocationSharingViewerViewModelType } private func listItemViewData(from userLiveLocation: UserLiveLocation) -> LiveLocationListItemViewData { + let isCurrentUser = liveLocationSharingViewerService.isCurrentUserId(userLiveLocation.userId) - let isCurrentUser = self.liveLocationSharingViewerService.isCurrentUserId(userLiveLocation.userId) - - let expirationDate = userLiveLocation.timestamp + userLiveLocation.timeout + let expirationDate = userLiveLocation.timestamp + userLiveLocation.timeout return LiveLocationListItemViewData(userId: userLiveLocation.userId, isCurrentUser: isCurrentUser, avatarData: userLiveLocation.avatarData, displayName: userLiveLocation.displayName, expirationDate: expirationDate, lastUpdate: userLiveLocation.lastUpdate) } private func update(with usersLiveLocation: [UserLiveLocation], highlightFirstLocation: Bool) { - - let annotations: [UserLocationAnnotation] = self.userLocationAnnotations(from: usersLiveLocation) + let annotations: [UserLocationAnnotation] = userLocationAnnotations(from: usersLiveLocation) var highlightedAnnotation: LocationAnnotation? if highlightFirstLocation { - highlightedAnnotation = self.getHighlightedAnnotation(from: annotations) + highlightedAnnotation = getHighlightedAnnotation(from: annotations) } - if let highlightableAnnotation = self.getHighlightedAnnotation(from: annotations) { - self.lastHighlightableAnnotation = highlightableAnnotation + if let highlightableAnnotation = getHighlightedAnnotation(from: annotations) { + lastHighlightableAnnotation = highlightableAnnotation } - if let lastHighlightableAnnotation = self.lastHighlightableAnnotation, usersLiveLocation.isEmpty { + if let lastHighlightableAnnotation = lastHighlightableAnnotation, usersLiveLocation.isEmpty { highlightedAnnotation = InvisibleLocationAnnotation(coordinate: lastHighlightableAnnotation.coordinate) } - let listViewItems = self.listItemsViewData(from: usersLiveLocation) + let listViewItems = listItemsViewData(from: usersLiveLocation) - self.state.annotations = annotations - self.state.highlightedAnnotation = highlightedAnnotation - self.state.listItemsViewData = listViewItems + state.annotations = annotations + state.highlightedAnnotation = highlightedAnnotation + state.listItemsViewData = listViewItems } private func highlighAnnotation(with userId: String) { - let foundUserAnnotation = self.state.annotations.first { annotation in + let foundUserAnnotation = state.annotations.first { annotation in annotation.userId == userId } @@ -219,20 +210,19 @@ class LiveLocationSharingViewerViewModel: LiveLocationSharingViewerViewModelType return } - self.state.highlightedAnnotation = foundUserAnnotation + state.highlightedAnnotation = foundUserAnnotation } private func stopUserLocationSharing() { + state.showLoadingIndicator = true - self.state.showLoadingIndicator = true - - self.liveLocationSharingViewerService.stopUserLiveLocationSharing { result in + liveLocationSharingViewerService.stopUserLiveLocationSharing { result in self.state.showLoadingIndicator = false switch result { case .success: break - case.failure: + case .failure: self.state.bindings.alertInfo = AlertInfo(id: .stopLocationSharingError, title: VectorL10n.error, message: VectorL10n.locationSharingLiveStopSharingError, diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModelProtocol.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModelProtocol.swift index a8626b528..890b96cf7 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol LiveLocationSharingViewerViewModelProtocol { - var completion: ((LiveLocationSharingViewerViewModelResult) -> Void)? { get set } var context: LiveLocationSharingViewerViewModelType.Context { get } } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/MockLiveLocationSharingViewerScreenState.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/MockLiveLocationSharingViewerScreenState.swift index 61353c169..a3451f669 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/MockLiveLocationSharingViewerScreenState.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/MockLiveLocationSharingViewerScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,12 +33,11 @@ enum MockLiveLocationSharingViewerScreenState: MockScreenState, CaseIterable { /// A list of screen state definitions static var allCases: [MockLiveLocationSharingViewerScreenState] { - return [.currentUser, .multipleUsers] + [.currentUser, .multipleUsers] } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { - + var screenView: ([Any], AnyView) { let service: LiveLocationSharingViewerServiceProtocol switch self { diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/LiveLocationSharingViewerServiceProtocol.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/LiveLocationSharingViewerServiceProtocol.swift index 3b0cbeeaa..cb9e107a5 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/LiveLocationSharingViewerServiceProtocol.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/LiveLocationSharingViewerServiceProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,12 +14,11 @@ // limitations under the License. // -import Foundation import Combine import CoreLocation +import Foundation protocol LiveLocationSharingViewerServiceProtocol { - /// All shared users live location var usersLiveLocation: [UserLiveLocation] { get } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/MatrixSDK/LiveLocationSharingViewerService.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/MatrixSDK/LiveLocationSharingViewerService.swift index f7b4155b6..9b646a1bf 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/MatrixSDK/LiveLocationSharingViewerService.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/MatrixSDK/LiveLocationSharingViewerService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,12 +14,11 @@ // limitations under the License. // -import Foundation import CoreLocation +import Foundation import MatrixSDK class LiveLocationSharingViewerService: LiveLocationSharingViewerServiceProtocol { - // MARK: - Properties private(set) var usersLiveLocation: [UserLiveLocation] = [] @@ -40,17 +39,17 @@ class LiveLocationSharingViewerService: LiveLocationSharingViewerServiceProtocol self.session = session self.roomId = roomId - self.updateUsersLiveLocation(notifyUpdate: false) + updateUsersLiveLocation(notifyUpdate: false) } // MARK: - Public func isCurrentUserId(_ userId: String) -> Bool { - return self.session.myUserId == userId + session.myUserId == userId } func startListeningLiveLocationUpdates() { - self.beaconInfoSummaryListener = self.session.aggregations.beaconAggregations.listenToBeaconInfoSummaryUpdateInRoom(withId: self.roomId) { [weak self] _ in + beaconInfoSummaryListener = session.aggregations.beaconAggregations.listenToBeaconInfoSummaryUpdateInRoom(withId: roomId) { [weak self] _ in self?.updateUsersLiveLocation(notifyUpdate: true) } @@ -58,17 +57,17 @@ class LiveLocationSharingViewerService: LiveLocationSharingViewerServiceProtocol func stopListeningLiveLocationUpdates() { if let listener = beaconInfoSummaryListener { - self.session.aggregations.removeListener(listener) - self.beaconInfoSummaryListener = nil + session.aggregations.removeListener(listener) + beaconInfoSummaryListener = nil } } func stopUserLiveLocationSharing(completion: @escaping (Result) -> Void) { - self.session.locationService.stopUserLocationSharing(inRoomWithId: roomId) { response in + session.locationService.stopUserLocationSharing(inRoomWithId: roomId) { response in switch response { case .success: - completion(.success(Void())) + completion(.success(())) case .failure(let error): completion(.failure(error)) } @@ -78,17 +77,16 @@ class LiveLocationSharingViewerService: LiveLocationSharingViewerServiceProtocol // MARK: - Private private func updateUsersLiveLocation(notifyUpdate: Bool) { - let beaconInfoSummaries = self.session.locationService.getDisplayableBeaconInfoSummaries(inRoomWithId: roomId) - self.usersLiveLocation = Self.usersLiveLocation(fromBeaconInfoSummaries: beaconInfoSummaries, session: session) + let beaconInfoSummaries = session.locationService.getDisplayableBeaconInfoSummaries(inRoomWithId: roomId) + usersLiveLocation = Self.usersLiveLocation(fromBeaconInfoSummaries: beaconInfoSummaries, session: session) if notifyUpdate { - self.didUpdateUsersLiveLocation?(self.usersLiveLocation) + didUpdateUsersLiveLocation?(usersLiveLocation) } } - class private func usersLiveLocation(fromBeaconInfoSummaries beaconInfoSummaries: [MXBeaconInfoSummaryProtocol], session: MXSession) -> [UserLiveLocation] { - - return beaconInfoSummaries.compactMap { beaconInfoSummary in + private class func usersLiveLocation(fromBeaconInfoSummaries beaconInfoSummaries: [MXBeaconInfoSummaryProtocol], session: MXSession) -> [UserLiveLocation] { + beaconInfoSummaries.compactMap { beaconInfoSummary in let beaconInfo = beaconInfoSummary.beaconInfo @@ -98,9 +96,9 @@ class LiveLocationSharingViewerService: LiveLocationSharingViewerServiceProtocol let avatarData = session.avatarInput(for: beaconInfoSummary.userId) - let timestamp = TimeInterval(beaconInfo.timestamp/1000) - let timeout = TimeInterval(beaconInfo.timeout/1000) - let lastUpdate = TimeInterval(lastBeacon.timestamp/1000) + let timestamp = TimeInterval(beaconInfo.timestamp / 1000) + let timeout = TimeInterval(beaconInfo.timeout / 1000) + let lastUpdate = TimeInterval(lastBeacon.timestamp / 1000) let coordinate = CLLocationCoordinate2D(latitude: lastBeacon.location.latitude, longitude: lastBeacon.location.longitude) diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/Mock/MockLiveLocationSharingViewerService.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/Mock/MockLiveLocationSharingViewerService.swift index ac192ec37..cfb34ef04 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/Mock/MockLiveLocationSharingViewerService.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/Mock/MockLiveLocationSharingViewerService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,12 +14,11 @@ // limitations under the License. // -import Foundation import Combine import CoreLocation +import Foundation class MockLiveLocationSharingViewerService: LiveLocationSharingViewerServiceProtocol { - // MARK: Properties private(set) var usersLiveLocation: [UserLiveLocation] = [] @@ -29,17 +28,15 @@ class MockLiveLocationSharingViewerService: LiveLocationSharingViewerServiceProt // MARK: Setup init(generateRandomUsers: Bool = false) { + let firstUserLiveLocation = createFirstUserLiveLocation() - let firstUserLiveLocation = self.createFirstUserLiveLocation() - - let secondUserLiveLocation = self.createSecondUserLiveLocation() + let secondUserLiveLocation = createSecondUserLiveLocation() var usersLiveLocation: [UserLiveLocation] = [firstUserLiveLocation, secondUserLiveLocation] - if generateRandomUsers { for _ in 1...20 { - let randomUser = self.createRandomUserLiveLocation() + let randomUser = createRandomUserLiveLocation() usersLiveLocation.append(randomUser) } } @@ -50,20 +47,14 @@ class MockLiveLocationSharingViewerService: LiveLocationSharingViewerServiceProt // MARK: Public func isCurrentUserId(_ userId: String) -> Bool { - return "@alice:matrix.org" == userId + userId == "@alice:matrix.org" } - func startListeningLiveLocationUpdates() { - - } + func startListeningLiveLocationUpdates() { } - func stopListeningLiveLocationUpdates() { - - } + func stopListeningLiveLocationUpdates() { } - func stopUserLiveLocationSharing(completion: @escaping (Result) -> Void) { - - } + func stopUserLiveLocationSharing(completion: @escaping (Result) -> Void) { } // MARK: Private @@ -80,7 +71,6 @@ class MockLiveLocationSharingViewerService: LiveLocationSharingViewerServiceProt } private func createSecondUserLiveLocation() -> UserLiveLocation { - let userAvatarData = AvatarInput(mxContentUri: nil, matrixItemId: "@bob:matrix.org", displayName: "Bob") let coordinate = CLLocationCoordinate2D(latitude: 51.4952641, longitude: -0.259096) @@ -93,9 +83,7 @@ class MockLiveLocationSharingViewerService: LiveLocationSharingViewerServiceProt return UserLiveLocation(avatarData: userAvatarData, timestamp: timestamp, timeout: timeout, lastUpdate: lastUpdate, coordinate: coordinate) } - private func createRandomUserLiveLocation() -> UserLiveLocation { - let uuidString = UUID().uuidString.suffix(8) let random = Double.random(in: 0.005...0.010) diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/UserLiveLocation.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/UserLiveLocation.swift index 015d09088..dc8c9898d 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/UserLiveLocation.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/UserLiveLocation.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,18 +14,17 @@ // limitations under the License. // -import Foundation import CoreLocation +import Foundation /// Represents user live location struct UserLiveLocation { - var userId: String { - return avatarData.matrixItemId + avatarData.matrixItemId } var displayName: String { - return avatarData.displayName ?? self.userId + avatarData.displayName ?? userId } let avatarData: AvatarInputProtocol diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Test/UI/LiveLocationSharingViewerUITests.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Test/UI/LiveLocationSharingViewerUITests.swift index 6d4333d4c..de996c2b0 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Test/UI/LiveLocationSharingViewerUITests.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Test/UI/LiveLocationSharingViewerUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class LiveLocationSharingViewerUITests: MockScreenTestCase { // Tests to be implemented. diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Test/Unit/LiveLocationSharingViewerViewModelTests.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Test/Unit/LiveLocationSharingViewerViewModelTests.swift index 39718e54d..cefe1c2ed 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Test/Unit/LiveLocationSharingViewerViewModelTests.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Test/Unit/LiveLocationSharingViewerViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,13 +14,12 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI class LiveLocationSharingViewerViewModelTests: XCTestCase { - var service: MockLiveLocationSharingViewerService! var viewModel: LiveLocationSharingViewerViewModelProtocol! var context: LiveLocationSharingViewerViewModelType.Context! diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationListItem.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationListItem.swift index b6af40483..e32de2818 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationListItem.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationListItem.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct LiveLocationListItem: View { - // MARK: - Properties // MARK: Private @@ -29,10 +28,9 @@ struct LiveLocationListItem: View { let viewData: LiveLocationListItemViewData var timeoutText: String { - let timeLeftString: String - if let elapsedTimeString = self.elapsedTimeString(from: viewData.expirationDate, isPastDate: false) { + if let elapsedTimeString = elapsedTimeString(from: viewData.expirationDate, isPastDate: false) { timeLeftString = VectorL10n.locationSharingLiveListItemTimeLeft(elapsedTimeString) } else { timeLeftString = VectorL10n.locationSharingLiveListItemSharingExpired @@ -42,10 +40,9 @@ struct LiveLocationListItem: View { } var lastUpdateText: String { - let timeLeftString: String - if let elapsedTimeString = self.elapsedTimeString(from: viewData.lastUpdate, isPastDate: true) { + if let elapsedTimeString = elapsedTimeString(from: viewData.lastUpdate, isPastDate: true) { timeLeftString = VectorL10n.locationSharingLiveListItemLastUpdate(elapsedTimeString) } else { timeLeftString = VectorL10n.locationSharingLiveListItemLastUpdateInvalid @@ -55,12 +52,12 @@ struct LiveLocationListItem: View { } var displayName: String { - return viewData.isCurrentUser ? VectorL10n.locationSharingLiveListItemCurrentUserDisplayName : viewData.displayName + viewData.isCurrentUser ? VectorL10n.locationSharingLiveListItemCurrentUserDisplayName : viewData.displayName } - var onStopSharingAction: (() -> (Void))? = nil + var onStopSharingAction: (() -> Void)? - var onBackgroundTap: ((String) -> (Void))? = nil + var onBackgroundTap: ((String) -> Void)? // MARK: - Body @@ -69,9 +66,9 @@ struct LiveLocationListItem: View { HStack(spacing: 18) { AvatarImage(avatarData: viewData.avatarData, size: .medium) .border() - VStack(alignment: .leading, spacing: 2) { Text(displayName) - .font(theme.fonts.bodySB) - .foregroundColor(theme.colors.primaryContent) + VStack(alignment: .leading, spacing: 2) { Text(displayName) + .font(theme.fonts.bodySB) + .foregroundColor(theme.colors.primaryContent) Text(timeoutText) .font(theme.fonts.caption1) .foregroundColor(theme.colors.primaryContent) @@ -97,7 +94,6 @@ struct LiveLocationListItem: View { // MARK: - Private private func elapsedTimeString(from timestamp: TimeInterval, isPastDate: Bool) -> String? { - let formatter = DateComponentsFormatter() formatter.unitsStyle = .abbreviated @@ -121,19 +117,16 @@ struct LiveLocationListItem: View { } struct LiveLocationListPreview: View { - let liveLocationSharingViewerService: LiveLocationSharingViewerServiceProtocol = MockLiveLocationSharingViewerService() var viewDataList: [LiveLocationListItemViewData] { - return self.listItemsViewData(from: liveLocationSharingViewerService.usersLiveLocation) + listItemsViewData(from: liveLocationSharingViewerService.usersLiveLocation) } var body: some View { VStack(alignment: .leading, spacing: 14) { ForEach(viewDataList) { viewData in - LiveLocationListItem(viewData: viewData, onStopSharingAction: { - - }, onBackgroundTap: { userId in + LiveLocationListItem(viewData: viewData, onStopSharingAction: { }, onBackgroundTap: { _ in }) } @@ -143,24 +136,22 @@ struct LiveLocationListPreview: View { } private func listItemsViewData(from usersLiveLocation: [UserLiveLocation]) -> [LiveLocationListItemViewData] { - var listItemsViewData: [LiveLocationListItemViewData] = [] let sortedUsersLiveLocation = usersLiveLocation.sorted { userLiveLocation1, userLiveLocation2 in - return userLiveLocation1.displayName > userLiveLocation2.displayName + userLiveLocation1.displayName > userLiveLocation2.displayName } - listItemsViewData = sortedUsersLiveLocation.map({ userLiveLocation in - return self.listItemViewData(from: userLiveLocation) - }) + listItemsViewData = sortedUsersLiveLocation.map { userLiveLocation in + self.listItemViewData(from: userLiveLocation) + } let currentUserIndex = listItemsViewData.firstIndex { viewData in - return viewData.isCurrentUser + viewData.isCurrentUser } // Move current user as first item if let currentUserIndex = currentUserIndex { - let currentUserViewData = listItemsViewData[currentUserIndex] listItemsViewData.remove(at: currentUserIndex) listItemsViewData.insert(currentUserViewData, at: 0) @@ -170,10 +161,9 @@ struct LiveLocationListPreview: View { } private func listItemViewData(from userLiveLocation: UserLiveLocation) -> LiveLocationListItemViewData { + let isCurrentUser = liveLocationSharingViewerService.isCurrentUserId(userLiveLocation.userId) - let isCurrentUser = self.liveLocationSharingViewerService.isCurrentUserId(userLiveLocation.userId) - - let expirationDate = userLiveLocation.timestamp + userLiveLocation.timeout + let expirationDate = userLiveLocation.timestamp + userLiveLocation.timeout return LiveLocationListItemViewData(userId: userLiveLocation.userId, isCurrentUser: isCurrentUser, avatarData: userLiveLocation.avatarData, displayName: userLiveLocation.displayName, expirationDate: expirationDate, lastUpdate: userLiveLocation.lastUpdate) } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationListItemViewData.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationListItemViewData.swift index 3e2392225..e87b0bd3d 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationListItemViewData.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationListItemViewData.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +18,8 @@ import Foundation /// View data for LiveLocationListItem struct LiveLocationListItemViewData: Identifiable { - var id: String { - return userId + userId } let userId: String diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift index 1fd256b5a..9678028b8 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,11 +14,10 @@ // limitations under the License. // -import SwiftUI import DSBottomSheet +import SwiftUI struct LiveLocationSharingViewer: View { - // MARK: - Properties // MARK: Private @@ -37,7 +36,6 @@ struct LiveLocationSharingViewer: View { var body: some View { ZStack(alignment: .bottom) { - if !viewModel.viewState.showMapLoadingError { LocationSharingMapView(tileServerMapURL: viewModel.viewState.mapStyleURL, annotations: viewModel.viewState.annotations, @@ -48,10 +46,10 @@ struct LiveLocationSharingViewer: View { userLocation: Binding.constant(nil), mapCenterCoordinate: Binding.constant(nil), onCalloutTap: { annotation in - if let userLocationAnnotation = annotation as? UserLocationAnnotation { - viewModel.send(viewAction: .share(userLocationAnnotation)) - } - }, + if let userLocationAnnotation = annotation as? UserLocationAnnotation { + viewModel.send(viewAction: .share(userLocationAnnotation)) + } + }, errorSubject: viewModel.viewState.errorSubject) // Show map credits above collapsed bottom sheet height if bottom sheet is visible @@ -61,7 +59,7 @@ struct LiveLocationSharingViewer: View { MapCreditsView(action: { viewModel.send(viewAction: .mapCreditsDidTap) }) - .offset(y: -(bottomSheetCollapsedHeight)) // Put the copyright action above the collapsed bottom sheet + .offset(y: -bottomSheetCollapsedHeight) // Put the copyright action above the collapsed bottom sheet .padding(.bottom, 10) } .ignoresSafeArea() @@ -112,7 +110,7 @@ struct LiveLocationSharingViewer: View { .background(theme.colors.system.ignoresSafeArea()) .bottomSheet(sheet, if: viewModel.viewState.isBottomSheetVisible) .actionSheet(isPresented: $viewModel.showMapCreditsSheet) { - return MapCreditsActionSheet(openURL: { url in + MapCreditsActionSheet(openURL: { url in openURL(url) }).sheet } @@ -142,8 +140,8 @@ struct LiveLocationSharingViewer: View { } // MARK: - Bottom sheet -extension LiveLocationSharingViewer { +extension LiveLocationSharingViewer { var sheetStyle: BottomSheetStyle { var bottomSheetStyle = BottomSheetStyle.standard @@ -162,9 +160,10 @@ extension LiveLocationSharingViewer { isExpanded: $isBottomSheetExpanded, minHeight: .points(bottomSheetCollapsedHeight), maxHeight: .available, - style: sheetStyle) { - userLocationList - } + style: sheetStyle + ) { + userLocationList + } } } diff --git a/RiotSwiftUI/Modules/LocationSharing/MapCredits/MapCreditsActionSheet.swift b/RiotSwiftUI/Modules/LocationSharing/MapCredits/MapCreditsActionSheet.swift index 3f08da5ac..927aceef0 100644 --- a/RiotSwiftUI/Modules/LocationSharing/MapCredits/MapCreditsActionSheet.swift +++ b/RiotSwiftUI/Modules/LocationSharing/MapCredits/MapCreditsActionSheet.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct MapCreditsActionSheet { - // Open URL action let openURL: (URL) -> Void diff --git a/RiotSwiftUI/Modules/LocationSharing/MapCredits/View/MapCreditsView.swift b/RiotSwiftUI/Modules/LocationSharing/MapCredits/View/MapCreditsView.swift index 3edb2fbfc..3d30f358b 100644 --- a/RiotSwiftUI/Modules/LocationSharing/MapCredits/View/MapCreditsView.swift +++ b/RiotSwiftUI/Modules/LocationSharing/MapCredits/View/MapCreditsView.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct MapCreditsView: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/LocationSharing/MapError/MapViewErrorAlertInfoBuilder.swift b/RiotSwiftUI/Modules/LocationSharing/MapError/MapViewErrorAlertInfoBuilder.swift index 9f1120d2a..fa02eef50 100644 --- a/RiotSwiftUI/Modules/LocationSharing/MapError/MapViewErrorAlertInfoBuilder.swift +++ b/RiotSwiftUI/Modules/LocationSharing/MapError/MapViewErrorAlertInfoBuilder.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,30 +17,27 @@ import Foundation struct MapViewErrorAlertInfoBuilder { - func build(with error: LocationSharingViewError, primaryButtonCompletion: (() -> Void)?) -> AlertInfo? { - let alertInfo: AlertInfo? switch error { case .failedLoadingMap: alertInfo = AlertInfo(id: .mapLoadingError, - title: VectorL10n.locationSharingLoadingMapErrorTitle(AppInfo.current.displayName), - primaryButton: (VectorL10n.ok, primaryButtonCompletion)) + title: VectorL10n.locationSharingLoadingMapErrorTitle(AppInfo.current.displayName), + primaryButton: (VectorL10n.ok, primaryButtonCompletion)) case .failedLocatingUser: alertInfo = AlertInfo(id: .userLocatingError, - title: VectorL10n.locationSharingLocatingUserErrorTitle(AppInfo.current.displayName), - primaryButton: (VectorL10n.ok, primaryButtonCompletion)) + title: VectorL10n.locationSharingLocatingUserErrorTitle(AppInfo.current.displayName), + primaryButton: (VectorL10n.ok, primaryButtonCompletion)) case .invalidLocationAuthorization: alertInfo = AlertInfo(id: .authorizationError, - title: VectorL10n.locationSharingInvalidAuthorizationErrorTitle(AppInfo.current.displayName), - primaryButton: (VectorL10n.locationSharingInvalidAuthorizationNotNow, primaryButtonCompletion), - secondaryButton: (VectorL10n.locationSharingInvalidAuthorizationSettings, primaryButtonCompletion)) + title: VectorL10n.locationSharingInvalidAuthorizationErrorTitle(AppInfo.current.displayName), + primaryButton: (VectorL10n.locationSharingInvalidAuthorizationNotNow, primaryButtonCompletion), + secondaryButton: (VectorL10n.locationSharingInvalidAuthorizationSettings, primaryButtonCompletion)) default: alertInfo = nil } return alertInfo } - } diff --git a/RiotSwiftUI/Modules/LocationSharing/MapError/View/MapLoadingErrorView.swift b/RiotSwiftUI/Modules/LocationSharing/MapError/View/MapLoadingErrorView.swift index 018d4a0ed..dc65ab4b5 100644 --- a/RiotSwiftUI/Modules/LocationSharing/MapError/View/MapLoadingErrorView.swift +++ b/RiotSwiftUI/Modules/LocationSharing/MapError/View/MapLoadingErrorView.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct MapLoadingErrorView: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/LocationSharing/MapView/LocationAnnotation.swift b/RiotSwiftUI/Modules/LocationSharing/MapView/LocationAnnotation.swift index 1ddec21c5..1bd69ff27 100644 --- a/RiotSwiftUI/Modules/LocationSharing/MapView/LocationAnnotation.swift +++ b/RiotSwiftUI/Modules/LocationSharing/MapView/LocationAnnotation.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import Mapbox /// Base class to handle a map annotation class LocationAnnotation: NSObject, MGLAnnotation { - // MARK: - Properties // Title property is needed to enable annotation selection and callout view showing @@ -36,15 +35,14 @@ class LocationAnnotation: NSObject, MGLAnnotation { } /// POI map annotation -class PinLocationAnnotation: LocationAnnotation {} +class PinLocationAnnotation: LocationAnnotation { } /// User map annotation class UserLocationAnnotation: LocationAnnotation { - // MARK: - Properties var userId: String { - return avatarData.matrixItemId + avatarData.matrixItemId } let avatarData: AvatarInputProtocol @@ -53,13 +51,12 @@ class UserLocationAnnotation: LocationAnnotation { init(avatarData: AvatarInputProtocol, coordinate: CLLocationCoordinate2D) { - self.avatarData = avatarData super.init(coordinate: coordinate) - super.title = self.avatarData.displayName ?? self.userId + super.title = self.avatarData.displayName ?? userId } } /// Invisible annotation -class InvisibleLocationAnnotation: LocationAnnotation {} +class InvisibleLocationAnnotation: LocationAnnotation { } diff --git a/RiotSwiftUI/Modules/LocationSharing/MapView/View/LocationSharingMapView.swift b/RiotSwiftUI/Modules/LocationSharing/MapView/View/LocationSharingMapView.swift index d244437ae..9b5073b33 100644 --- a/RiotSwiftUI/Modules/LocationSharing/MapView/View/LocationSharingMapView.swift +++ b/RiotSwiftUI/Modules/LocationSharing/MapView/View/LocationSharingMapView.swift @@ -14,15 +14,14 @@ // limitations under the License. // -import SwiftUI import Combine import Mapbox +import SwiftUI struct LocationSharingMapView: UIViewRepresentable { - // MARK: - Constants - private struct Constants { + private enum Constants { static let mapZoomLevel = 15.0 } @@ -41,10 +40,10 @@ struct LocationSharingMapView: UIViewRepresentable { let userAvatarData: AvatarInputProtocol? /// True to indicate to show and follow current user location - var showsUserLocation: Bool = false + var showsUserLocation = false /// True to indicate that a touch on user annotation can show a callout - var userAnnotationCanShowCallout: Bool = false + var userAnnotationCanShowCallout = false /// Last user location if `showsUserLocation` has been enabled @Binding var userLocation: CLLocationCoordinate2D? @@ -64,8 +63,7 @@ struct LocationSharingMapView: UIViewRepresentable { // MARK: - UIViewRepresentable func makeUIView(context: Context) -> MGLMapView { - - let mapView = self.makeMapView() + let mapView = makeMapView() mapView.delegate = context.coordinator let panGesture = UIPanGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.didPan)) panGesture.delegate = context.coordinator @@ -74,15 +72,14 @@ struct LocationSharingMapView: UIViewRepresentable { } func updateUIView(_ mapView: MGLMapView, context: Context) { - mapView.vc_removeAllAnnotations() - mapView.addAnnotations(self.annotations) + mapView.addAnnotations(annotations) - if let highlightedAnnotation = self.highlightedAnnotation { + if let highlightedAnnotation = highlightedAnnotation { mapView.setCenter(highlightedAnnotation.coordinate, zoomLevel: Constants.mapZoomLevel, animated: false) } - if self.showsUserLocation { + if showsUserLocation { mapView.showsUserLocation = true mapView.userTrackingMode = .follow } else { @@ -108,24 +105,22 @@ struct LocationSharingMapView: UIViewRepresentable { } // MARK: - Coordinator + extension LocationSharingMapView { - class Coordinator: NSObject, MGLMapViewDelegate, UIGestureRecognizerDelegate { - // MARK: - Properties var locationSharingMapView: LocationSharingMapView // MARK: - Setup - init(_ locationSharingMapView: LocationSharingMapView) { - self.locationSharingMapView = locationSharingMapView - } + init(_ locationSharingMapView: LocationSharingMapView) { + self.locationSharingMapView = locationSharingMapView + } // MARK: - MGLMapViewDelegate func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? { - if let userLocationAnnotation = annotation as? UserLocationAnnotation { return LocationAnnotationView(userLocationAnnotation: userLocationAnnotation) } else if let pinLocationAnnotation = annotation as? PinLocationAnnotation { @@ -168,7 +163,7 @@ extension LocationSharingMapView { // MARK: Callout func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool { - return annotation is UserLocationAnnotation && locationSharingMapView.userAnnotationCanShowCallout + annotation is UserLocationAnnotation && locationSharingMapView.userAnnotationCanShowCallout } func mapView(_ mapView: MGLMapView, calloutViewFor annotation: MGLAnnotation) -> MGLCalloutView? { @@ -188,7 +183,7 @@ extension LocationSharingMapView { // MARK: UIGestureRecognizer func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { - return gestureRecognizer is UIPanGestureRecognizer + gestureRecognizer is UIPanGestureRecognizer } @objc @@ -199,12 +194,12 @@ extension LocationSharingMapView { } // MARK: - MGLMapView convenient methods + extension MGLMapView { - func vc_removeAllAnnotations() { - guard let annotations = self.annotations else { + guard let annotations = annotations else { return } - self.removeAnnotations(annotations) + removeAnnotations(annotations) } } diff --git a/RiotSwiftUI/Modules/LocationSharing/MapView/View/LocationSharingMarkerView.swift b/RiotSwiftUI/Modules/LocationSharing/MapView/View/LocationSharingMarkerView.swift index 0450b16a3..7cf7f7e4a 100644 --- a/RiotSwiftUI/Modules/LocationSharing/MapView/View/LocationSharingMarkerView.swift +++ b/RiotSwiftUI/Modules/LocationSharing/MapView/View/LocationSharingMarkerView.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct LocationSharingMarkerView: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserAnnotationCalloutContentView.swift b/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserAnnotationCalloutContentView.swift index 419213972..556b76030 100644 --- a/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserAnnotationCalloutContentView.swift +++ b/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserAnnotationCalloutContentView.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import Foundation import Reusable class UserAnnotationCalloutContentView: UIView, Themable, NibLoadable { - // MARK: - Constants private static let sizingView = UserAnnotationCalloutContentView.instantiate() @@ -39,16 +38,16 @@ class UserAnnotationCalloutContentView: UIView, Themable, NibLoadable { // MARK: - Setup static func instantiate() -> UserAnnotationCalloutContentView { - return UserAnnotationCalloutContentView.loadFromNib() + UserAnnotationCalloutContentView.loadFromNib() } // MARK: - Public func update(theme: Theme) { - self.backgroundView.backgroundColor = theme.colors.background - self.titleLabel.textColor = theme.colors.secondaryContent - self.titleLabel.font = theme.fonts.callout - self.shareButton.tintColor = theme.colors.secondaryContent + backgroundView.backgroundColor = theme.colors.background + titleLabel.textColor = theme.colors.secondaryContent + titleLabel.font = theme.fonts.callout + shareButton.tintColor = theme.colors.secondaryContent } // MARK: - Life cycle @@ -56,18 +55,18 @@ class UserAnnotationCalloutContentView: UIView, Themable, NibLoadable { override func awakeFromNib() { super.awakeFromNib() - self.titleLabel.text = VectorL10n.locationSharingLiveMapCalloutTitle - self.backgroundView.layer.masksToBounds = true + titleLabel.text = VectorL10n.locationSharingLiveMapCalloutTitle + backgroundView.layer.masksToBounds = true } override func layoutSubviews() { super.layoutSubviews() - self.backgroundView.layer.cornerRadius = Constants.cornerRadius + backgroundView.layer.cornerRadius = Constants.cornerRadius } static func contentViewSize() -> CGSize { - let sizingView = self.sizingView + let sizingView = sizingView sizingView.frame = CGRect(x: 0, y: 0, width: 1, height: Constants.height) @@ -77,8 +76,8 @@ class UserAnnotationCalloutContentView: UIView, Themable, NibLoadable { let fittingSize = CGSize(width: UIView.layoutFittingCompressedSize.width, height: Constants.height) let size = sizingView.systemLayoutSizeFitting(fittingSize, - withHorizontalFittingPriority: .fittingSizeLevel, - verticalFittingPriority: .required) + withHorizontalFittingPriority: .fittingSizeLevel, + verticalFittingPriority: .required) return size } diff --git a/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserAnnotationCalloutView.swift b/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserAnnotationCalloutView.swift index 64edcfd30..f405c179a 100644 --- a/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserAnnotationCalloutView.swift +++ b/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserAnnotationCalloutView.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Mapbox class UserAnnotationCalloutView: UIView, MGLCalloutView, Themable { - // MARK: - Constants private enum Constants { @@ -31,16 +30,16 @@ class UserAnnotationCalloutView: UIView, MGLCalloutView, Themable { var representedObject: MGLAnnotation - lazy var leftAccessoryView: UIView = UIView() + lazy var leftAccessoryView = UIView() - lazy var rightAccessoryView: UIView = UIView() + lazy var rightAccessoryView = UIView() var delegate: MGLCalloutViewDelegate? // Allow the callout to remain open during panning. - let dismissesAutomatically: Bool = false + let dismissesAutomatically = false - let isAnchoredToAnnotation: Bool = true + let isAnchoredToAnnotation = true // https://github.com/mapbox/mapbox-gl-native/issues/9228 override var center: CGPoint { @@ -50,33 +49,31 @@ class UserAnnotationCalloutView: UIView, MGLCalloutView, Themable { super.center = newCenter } get { - return super.center + super.center } } // MARK: Private - lazy var contentView: UserAnnotationCalloutContentView = { - return UserAnnotationCalloutContentView.instantiate() - }() + lazy var contentView = UserAnnotationCalloutContentView.instantiate() // MARK: - Setup required init(userLocationAnnotation: UserLocationAnnotation) { - - self.representedObject = userLocationAnnotation + representedObject = userLocationAnnotation super.init(frame: .zero) - self.vc_addSubViewMatchingParent(self.contentView) + vc_addSubViewMatchingParent(contentView) - self.update(theme: ThemeService.shared().theme) + update(theme: ThemeService.shared().theme) let size = UserAnnotationCalloutContentView.contentViewSize() - self.frame = CGRect(origin: .zero, size: size) + frame = CGRect(origin: .zero, size: size) } + @available(*, unavailable) required init?(coder decoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -84,16 +81,15 @@ class UserAnnotationCalloutView: UIView, MGLCalloutView, Themable { // MARK: - Public func update(theme: Theme) { - self.contentView.update(theme: theme) + contentView.update(theme: theme) } // MARK: - Overrides func presentCallout(from rect: CGRect, in view: UIView, constrainedTo constrainedRect: CGRect, animated: Bool) { - // Set callout above the marker view - self.center = view.center.applying(CGAffineTransform(translationX: 0, y: view.bounds.height/2 + self.bounds.height)) + center = view.center.applying(CGAffineTransform(translationX: 0, y: view.bounds.height / 2 + bounds.height)) delegate?.calloutViewWillAppear?(self) @@ -101,10 +97,10 @@ class UserAnnotationCalloutView: UIView, MGLCalloutView, Themable { if isCalloutTappable() { // Handle taps and eventually try to send them to the delegate (usually the map view). - self.contentView.shareButton.addTarget(self, action: #selector(UserAnnotationCalloutView.calloutTapped), for: .touchUpInside) + contentView.shareButton.addTarget(self, action: #selector(UserAnnotationCalloutView.calloutTapped), for: .touchUpInside) } else { // Disable tapping and highlighting. - self.contentView.shareButton.isUserInteractionEnabled = false + contentView.shareButton.isUserInteractionEnabled = false } if animated { @@ -124,7 +120,7 @@ class UserAnnotationCalloutView: UIView, MGLCalloutView, Themable { } func dismissCallout(animated: Bool) { - if (superview != nil) { + if superview != nil { if animated { UIView.animate(withDuration: Constants.animationDuration, animations: { [weak self] in self?.alpha = 0 @@ -149,7 +145,7 @@ class UserAnnotationCalloutView: UIView, MGLCalloutView, Themable { } @objc func calloutTapped() { - if isCalloutTappable() && delegate!.responds(to: #selector(MGLCalloutViewDelegate.calloutViewTapped)) { + if isCalloutTappable(), delegate!.responds(to: #selector(MGLCalloutViewDelegate.calloutViewTapped)) { delegate!.calloutViewTapped!(self) } } diff --git a/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserLocationAnnotationView.swift b/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserLocationAnnotationView.swift index 4662ae485..25a5b9848 100644 --- a/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserLocationAnnotationView.swift +++ b/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserLocationAnnotationView.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,11 +15,10 @@ // import Foundation -import SwiftUI import Mapbox +import SwiftUI class LocationAnnotationView: MGLUserLocationAnnotationView { - // MARK: - Constants private enum Constants { @@ -34,31 +33,30 @@ class LocationAnnotationView: MGLUserLocationAnnotationView { override init(annotation: MGLAnnotation?, reuseIdentifier: String?) { super.init(annotation: annotation, reuseIdentifier: - reuseIdentifier) - self.frame = Constants.defaultFrame + reuseIdentifier) + frame = Constants.defaultFrame } convenience init(avatarData: AvatarInputProtocol) { self.init(annotation: nil, reuseIdentifier: nil) - self.addUserMarkerView(with: avatarData) + addUserMarkerView(with: avatarData) } convenience init(userLocationAnnotation: UserLocationAnnotation) { - // TODO: Use a reuseIdentifier self.init(annotation: userLocationAnnotation, reuseIdentifier: nil) - self.addUserMarkerView(with: userLocationAnnotation.avatarData) + addUserMarkerView(with: userLocationAnnotation.avatarData) } convenience init(pinLocationAnnotation: PinLocationAnnotation) { - // TODO: Use a reuseIdentifier self.init(annotation: pinLocationAnnotation, reuseIdentifier: nil) - self.addPinMarkerView() + addPinMarkerView() } + @available(*, unavailable) required init?(coder: NSCoder) { fatalError() } @@ -66,7 +64,6 @@ class LocationAnnotationView: MGLUserLocationAnnotationView { // MARK: - Private private func addUserMarkerView(with avatarData: AvatarInputProtocol) { - guard let avatarMarkerView = UIHostingController(rootView: LocationSharingMarkerView(backgroundColor: theme.userColor(for: avatarData.matrixItemId)) { AvatarImage(avatarData: avatarData, size: .medium) .border() @@ -90,11 +87,10 @@ class LocationAnnotationView: MGLUserLocationAnnotationView { } private func addMarkerView(_ markerView: UIView) { - markerView.backgroundColor = .clear addSubview(markerView) - markerView.frame = self.bounds + markerView.frame = bounds } } diff --git a/RiotSwiftUI/Modules/LocationSharing/ShareLocationActivity/ShareLocationActivityController.swift b/RiotSwiftUI/Modules/LocationSharing/ShareLocationActivity/ShareLocationActivityController.swift index 4b3e6ca72..6c5f00154 100644 --- a/RiotSwiftUI/Modules/LocationSharing/ShareLocationActivity/ShareLocationActivityController.swift +++ b/RiotSwiftUI/Modules/LocationSharing/ShareLocationActivity/ShareLocationActivityController.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,16 +14,15 @@ // limitations under the License. // -import Foundation import CoreLocation +import Foundation /// Build a UIActivityViewController to share a location class ShareLocationActivityControllerBuilder { - func build(with location: CLLocationCoordinate2D) -> UIActivityViewController { - return UIActivityViewController(activityItems: [ShareToMapsAppActivity.urlForMapsAppType(.apple, location: location)], - applicationActivities: [ShareToMapsAppActivity(type: .apple, location: location), - ShareToMapsAppActivity(type: .google, location: location), - ShareToMapsAppActivity(type: .osm, location: location)]) + UIActivityViewController(activityItems: [ShareToMapsAppActivity.urlForMapsAppType(.apple, location: location)], + applicationActivities: [ShareToMapsAppActivity(type: .apple, location: location), + ShareToMapsAppActivity(type: .google, location: location), + ShareToMapsAppActivity(type: .osm, location: location)]) } } diff --git a/RiotSwiftUI/Modules/LocationSharing/ShareLocationActivity/ShareToMapsAppActivity.swift b/RiotSwiftUI/Modules/LocationSharing/ShareLocationActivity/ShareToMapsAppActivity.swift index 53b96121d..1d325e733 100644 --- a/RiotSwiftUI/Modules/LocationSharing/ShareLocationActivity/ShareToMapsAppActivity.swift +++ b/RiotSwiftUI/Modules/LocationSharing/ShareLocationActivity/ShareToMapsAppActivity.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import UIKit import CoreLocation +import UIKit extension UIActivity.ActivityType { static let shareToMapsApp = UIActivity.ActivityType("Element.ShareToMapsApp") @@ -31,7 +31,7 @@ class ShareToMapsAppActivity: UIActivity { private let type: MapsAppType private let location: CLLocationCoordinate2D - private override init() { + override private init() { fatalError() } @@ -63,15 +63,15 @@ class ShareToMapsAppActivity: UIActivity { } var activityCategory: UIActivity.Category { - return .action + .action } override var activityType: UIActivity.ActivityType { - return .shareToMapsApp + .shareToMapsApp } override func canPerform(withActivityItems activityItems: [Any]) -> Bool { - return true + true } override func prepare(withActivityItems activityItems: [Any]) { diff --git a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Coordinator/LocationSharingCoordinator.swift b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Coordinator/LocationSharingCoordinator.swift index b6d0669cd..266d5e03b 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Coordinator/LocationSharingCoordinator.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Coordinator/LocationSharingCoordinator.swift @@ -15,9 +15,9 @@ // import Foundation -import UIKit -import SwiftUI import MatrixSDK +import SwiftUI +import UIKit struct LocationSharingCoordinatorParameters { let session: MXSession @@ -57,7 +57,6 @@ extension LocationSharingCoordinateType { } final class LocationSharingCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -83,7 +82,8 @@ final class LocationSharingCoordinator: Coordinator, Presentable { mapStyleURL: parameters.session.vc_homeserverConfiguration().tileServer.mapStyleURL, avatarData: parameters.avatarData, isLiveLocationSharingEnabled: true, - service: locationSharingService) + service: locationSharingService + ) let view = LocationSharingView(context: viewModel.context) .addDependency(AvatarService.instantiate(mediaManager: parameters.mediaManager)) @@ -93,6 +93,7 @@ final class LocationSharingCoordinator: Coordinator, Presentable { } // MARK: - Public + func start() { locationSharingViewModel.completion = { [weak self] result in guard let self = self else { return } @@ -111,22 +112,22 @@ final class LocationSharingCoordinator: Coordinator, Presentable { } static func shareLocationActivityController(_ location: CLLocationCoordinate2D) -> UIActivityViewController { - return UIActivityViewController(activityItems: [ShareToMapsAppActivity.urlForMapsAppType(.apple, location: location)], - applicationActivities: [ShareToMapsAppActivity(type: .apple, location: location), - ShareToMapsAppActivity(type: .google, location: location), - ShareToMapsAppActivity(type: .osm, location: location)]) + UIActivityViewController(activityItems: [ShareToMapsAppActivity.urlForMapsAppType(.apple, location: location)], + applicationActivities: [ShareToMapsAppActivity(type: .apple, location: location), + ShareToMapsAppActivity(type: .google, location: location), + ShareToMapsAppActivity(type: .osm, location: location)]) } // MARK: - Private private func presentShareLocationActivity(with location: CLLocationCoordinate2D) { - self.locationSharingHostingController.present(Self.shareLocationActivityController(location), animated: true) + locationSharingHostingController.present(Self.shareLocationActivityController(location), animated: true) } private func shareStaticLocation(latitude: Double, longitude: Double, coordinateType: LocationSharingCoordinateType) { - self.locationSharingViewModel.startLoading() + locationSharingViewModel.startLoading() - self.parameters.roomDataSource.sendLocation(withLatitude: latitude, longitude: longitude, description: nil, coordinateType: coordinateType.eventAssetType()) { [weak self] _ in + parameters.roomDataSource.sendLocation(withLatitude: latitude, longitude: longitude, description: nil, coordinateType: coordinateType.eventAssetType()) { [weak self] _ in guard let self = self else { return } self.locationSharingViewModel.stopLoading() @@ -140,8 +141,8 @@ final class LocationSharingCoordinator: Coordinator, Presentable { } private func startLiveLocationSharing(with timeout: TimeInterval) { - guard let locationService = self.parameters.roomDataSource.mxSession.locationService, let roomId = self.parameters.roomDataSource.roomId else { - self.locationSharingViewModel.stopLoading(error: .locationSharingError) + guard let locationService = parameters.roomDataSource.mxSession.locationService, let roomId = parameters.roomDataSource.roomId else { + locationSharingViewModel.stopLoading(error: .locationSharingError) return } @@ -166,16 +167,15 @@ final class LocationSharingCoordinator: Coordinator, Presentable { } private func checkLiveLocationCanBeStarted(completion: @escaping ((Result) -> Void)) { - - guard self.canShareLiveLocation() else { + guard canShareLiveLocation() else { completion(.failure(LiveLocationStartError.powerLevelNotHighEnough)) return } - self.showLabFlagPromotionIfNeeded { labFlagEnabled in + showLabFlagPromotionIfNeeded { labFlagEnabled in if labFlagEnabled { - completion(.success(Void())) + completion(.success(())) } else { completion(.failure(LiveLocationStartError.labFlagNotEnabled)) } @@ -184,18 +184,18 @@ final class LocationSharingCoordinator: Coordinator, Presentable { // Check if user can send beacon info state event private func canShareLiveLocation() -> Bool { - guard let myUserId = self.parameters.roomDataSource.mxSession.myUserId else { + guard let myUserId = parameters.roomDataSource.mxSession.myUserId else { return false } - let userPowerLevelRawValue = self.parameters.roomDataSource.roomState.powerLevels.powerLevelOfUser(withUserID: myUserId) + let userPowerLevelRawValue = parameters.roomDataSource.roomState.powerLevels.powerLevelOfUser(withUserID: myUserId) guard let userPowerLevel = RoomPowerLevel(rawValue: userPowerLevelRawValue) else { return false } return userPowerLevel.rawValue >= RoomPowerLevel.moderator.rawValue - } + } private func showLabFlagPromotionIfNeeded(completion: @escaping ((Bool) -> Void)) { guard RiotSettings.shared.enableLiveLocationSharing == false else { @@ -204,11 +204,10 @@ final class LocationSharingCoordinator: Coordinator, Presentable { return } - self.showLabFlagPromotion(completion: completion) + showLabFlagPromotion(completion: completion) } private func showLabFlagPromotion(completion: @escaping ((Bool) -> Void)) { - // TODO: Use a NavigationRouter instead of using NavigationView inside LocationSharingView // In order to use `NavigationRouter.present` @@ -224,14 +223,14 @@ final class LocationSharingCoordinator: Coordinator, Presentable { } } - self.locationSharingHostingController.present(coordinator.toPresentable(), animated: true) + locationSharingHostingController.present(coordinator.toPresentable(), animated: true) - self.add(childCoordinator: coordinator) + add(childCoordinator: coordinator) } // MARK: - Presentable func toPresentable() -> UIViewController { - return locationSharingHostingController + locationSharingHostingController } } diff --git a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingModels.swift b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingModels.swift index 1a5304b87..3178bcb87 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingModels.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingModels.swift @@ -14,10 +14,10 @@ // limitations under the License. // -import Foundation -import SwiftUI import Combine import CoreLocation +import Foundation +import SwiftUI // This is the equivalent of MXEventAssetType in the MatrixSDK enum LocationSharingCoordinateType { @@ -27,9 +27,9 @@ enum LocationSharingCoordinateType { enum LiveLocationSharingTimeout: TimeInterval { // Timer are in milliseconde because timestamp are in millisecond in Matrix SDK - case short = 900000 // 15 minutes - case medium = 3600000 // 1 hour - case long = 28800000 // 8 hours + case short = 900_000 // 15 minutes + case medium = 3_600_000 // 1 hour + case long = 28_800_000 // 8 hours } enum LocationSharingViewAction { @@ -47,7 +47,7 @@ enum LocationSharingViewModelResult { case cancel case share(latitude: Double, longitude: Double, coordinateType: LocationSharingCoordinateType) case shareLiveLocation(timeout: TimeInterval) - case checkLiveLocationCanBeStarted(_ completion: ((Result) -> Void)) + case checkLiveLocationCanBeStarted(_ completion: (Result) -> Void) } enum LiveLocationStartError: Error { @@ -63,7 +63,6 @@ enum LocationSharingViewError { } struct LocationSharingViewState: BindableState { - /// Map style URL let mapStyleURL: URL @@ -77,21 +76,21 @@ struct LocationSharingViewState: BindableState { var highlightedAnnotation: LocationAnnotation? /// Indicates whether the user has moved around the map to drop a pin somewhere other than their current location - var isPinDropSharing: Bool = false + var isPinDropSharing = false - var showLoadingIndicator: Bool = false + var showLoadingIndicator = false /// True to indicate to show and follow current user location - var showsUserLocation: Bool = false + var showsUserLocation = false /// Used to hide live location sharing features - var isLiveLocationSharingEnabled: Bool = false + var isLiveLocationSharingEnabled = false var shareButtonEnabled: Bool { !showLoadingIndicator } - var showMapLoadingError: Bool = false + var showMapLoadingError = false let errorSubject = PassthroughSubject() diff --git a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingScreenState.swift b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingScreenState.swift index defbf11fc..3b86f4e89 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingScreenState.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,9 +14,9 @@ // limitations under the License. // +import CoreLocation import Foundation import SwiftUI -import CoreLocation enum MockLocationSharingScreenState: MockScreenState, CaseIterable { case shareUserLocation @@ -25,8 +25,7 @@ enum MockLocationSharingScreenState: MockScreenState, CaseIterable { LocationSharingView.self } - var screenView: ([Any], AnyView) { - + var screenView: ([Any], AnyView) { let locationSharingService = MockLocationSharingService() let mapStyleURL = URL(string: "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx")! @@ -35,6 +34,6 @@ enum MockLocationSharingScreenState: MockScreenState, CaseIterable { isLiveLocationSharingEnabled: true, service: locationSharingService) return ([viewModel], AnyView(LocationSharingView(context: viewModel.context) - .addDependency(MockAvatarService.example))) + .addDependency(MockAvatarService.example))) } } diff --git a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingViewModel.swift b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingViewModel.swift index 56cdb1f1c..cbde1a243 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingViewModel.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingViewModel.swift @@ -14,15 +14,14 @@ // limitations under the License. // -import SwiftUI import Combine import CoreLocation +import SwiftUI typealias LocationSharingViewModelType = StateStoreViewModel + Never, + LocationSharingViewAction> class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -36,8 +35,7 @@ class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingVie // MARK: - Setup init(mapStyleURL: URL, avatarData: AvatarInputProtocol, isLiveLocationSharingEnabled: Bool = false, service: LocationSharingServiceProtocol) { - - self.locationSharingService = service + locationSharingService = service let viewState = LocationSharingViewState(mapStyleURL: mapStyleURL, userAvatarData: avatarData, @@ -79,7 +77,7 @@ class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingVie state.showsUserLocation = true state.isPinDropSharing = false case .startLiveSharing: - self.startLiveLocationSharing() + startLiveLocationSharing() case .shareLiveLocation(let timeout): state.bindings.showingTimerSelector = false completion?(.shareLiveLocation(timeout: timeout.rawValue)) @@ -101,8 +99,7 @@ class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingVie state.showLoadingIndicator = false if let error = error { - - let alertInfo: AlertInfo + let alertInfo: AlertInfo switch error { case .locationSharingPowerLevelError: @@ -128,7 +125,7 @@ class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingVie return } - let primaryButtonCompletion = { [weak self] () -> Void in + let primaryButtonCompletion: (() -> Void)? = { [weak self] () in self?.completion?(.cancel) } @@ -149,16 +146,15 @@ class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingVie title: VectorL10n.locationSharingInvalidAuthorizationErrorTitle(AppInfo.current.displayName), primaryButton: (VectorL10n.locationSharingInvalidAuthorizationNotNow, primaryButtonCompletion), secondaryButton: (VectorL10n.locationSharingInvalidAuthorizationSettings, { - UIApplication.shared.vc_openSettings() - })) + UIApplication.shared.vc_openSettings() + })) default: break } } private func checkLocationAuthorizationAndPresentTimerSelector() { - - self.locationSharingService.requestAuthorization { [weak self] authorizationStatus in + locationSharingService.requestAuthorization { [weak self] authorizationStatus in guard let self = self else { return @@ -168,15 +164,15 @@ class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingVie case .unknown, .denied: // Show error alert self.state.bindings.alertInfo = AlertInfo(id: .userLocatingError, - title: VectorL10n.locationSharingLocatingUserErrorTitle(AppInfo.current.displayName), + title: VectorL10n.locationSharingLocatingUserErrorTitle(AppInfo.current.displayName), primaryButton: (VectorL10n.ok, { UIApplication.shared.vc_openSettings() - })) + })) case .authorizedInForeground: // When user only authorized location in foreground, advize to use background location self.state.bindings.alertInfo = AlertInfo(id: .userLocatingError, title: VectorL10n.locationSharingAllowBackgroundLocationTitle, - message: VectorL10n.locationSharingAllowBackgroundLocationMessage, - primaryButton: (VectorL10n.locationSharingAllowBackgroundLocationCancelAction, {}), + message: VectorL10n.locationSharingAllowBackgroundLocationMessage, + primaryButton: (VectorL10n.locationSharingAllowBackgroundLocationCancelAction, { }), secondaryButton: (VectorL10n.locationSharingAllowBackgroundLocationValidateAction, { UIApplication.shared.vc_openSettings() })) case .authorizedAlways: self.state.bindings.showingTimerSelector = true @@ -185,12 +181,11 @@ class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingVie } private func startLiveLocationSharing() { - guard let completion = completion else { return } - completion(.checkLiveLocationCanBeStarted({ result in + completion(.checkLiveLocationCanBeStarted { result in switch result { case .success: @@ -200,6 +195,6 @@ class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingVie self.stopLoading(error: .locationSharingPowerLevelError) } } - })) + }) } } diff --git a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingViewModelProtocol.swift b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingViewModelProtocol.swift index a15349a3b..eeed96aff 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Service/LocationSharingServiceProtocol.swift b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Service/LocationSharingServiceProtocol.swift index 64cfac575..288a7cc7a 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Service/LocationSharingServiceProtocol.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Service/LocationSharingServiceProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,15 +14,14 @@ // limitations under the License. // -import Foundation import Combine import CoreLocation +import Foundation /// Location authorization request handler typealias LocationAuthorizationHandler = (_ authorizationStatus: LocationAuthorizationStatus) -> Void protocol LocationSharingServiceProtocol { - /// Request location authorization func requestAuthorization(_ handler: @escaping LocationAuthorizationHandler) } diff --git a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Service/MatrixSDK/LocationSharingService.swift b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Service/MatrixSDK/LocationSharingService.swift index d7692c29e..980220384 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Service/MatrixSDK/LocationSharingService.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Service/MatrixSDK/LocationSharingService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,12 +14,11 @@ // limitations under the License. // -import Foundation import CoreLocation +import Foundation import MatrixSDK class LocationSharingService: LocationSharingServiceProtocol { - // MARK: - Properties // MARK: Private @@ -27,7 +26,7 @@ class LocationSharingService: LocationSharingServiceProtocol { private let session: MXSession private var userLocationService: UserLocationServiceProtocol? { - return self.session.userLocationService + session.userLocationService } // MARK: Public @@ -41,8 +40,7 @@ class LocationSharingService: LocationSharingServiceProtocol { // MARK: - Public func requestAuthorization(_ handler: @escaping LocationAuthorizationHandler) { - guard let userLocationService = self.userLocationService else { - + guard let userLocationService = userLocationService else { MXLog.error("[LocationSharingService] No userLocationService found for the current session") handler(LocationAuthorizationStatus.unknown) return diff --git a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Service/Mock/MockLocationSharingService.swift b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Service/Mock/MockLocationSharingService.swift index 8b8de5a5a..a36d3b726 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Service/Mock/MockLocationSharingService.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Service/Mock/MockLocationSharingService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,9 +14,9 @@ // limitations under the License. // -import Foundation import Combine import CoreLocation +import Foundation class MockLocationSharingService: LocationSharingServiceProtocol { func requestAuthorization(_ handler: @escaping LocationAuthorizationHandler) { diff --git a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Test/UI/LocationSharingUITests.swift b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Test/UI/LocationSharingUITests.swift index d9fb19faa..0b55053f8 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Test/UI/LocationSharingUITests.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Test/UI/LocationSharingUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class LocationSharingUITests: MockScreenTestCase { func testInitialUserLocation() { diff --git a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Test/Unit/LocationSharingViewModelTests.swift b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Test/Unit/LocationSharingViewModelTests.swift index 1eacdee00..1914b4478 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Test/Unit/LocationSharingViewModelTests.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Test/Unit/LocationSharingViewModelTests.swift @@ -14,14 +14,13 @@ // limitations under the License. // -import XCTest import Combine import CoreLocation +import XCTest @testable import RiotSwiftUI class LocationSharingViewModelTests: XCTestCase { - var cancellables = Set() func testInitialState() { @@ -40,7 +39,7 @@ class LocationSharingViewModelTests: XCTestCase { func testCancellation() { let viewModel = buildViewModel() - let expectation = self.expectation(description: "Cancellation completion should be invoked") + let expectation = expectation(description: "Cancellation completion should be invoked") viewModel.completion = { result in switch result { @@ -95,10 +94,9 @@ class LocationSharingViewModelTests: XCTestCase { } private func buildViewModel() -> LocationSharingViewModel { - let service = MockLocationSharingService() return LocationSharingViewModel(mapStyleURL: URL(string: "http://empty.com")!, - avatarData: AvatarInput(mxContentUri: "", matrixItemId: "", displayName: ""), service: service) + avatarData: AvatarInput(mxContentUri: "", matrixItemId: "", displayName: ""), service: service) } } diff --git a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/View/LocationSharingOptionButton.swift b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/View/LocationSharingOptionButton.swift index 964784f57..556f410f4 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/View/LocationSharingOptionButton.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/View/LocationSharingOptionButton.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct LocationSharingOptionButton: View { - // MARK: - Properties // MARK: Private @@ -25,7 +24,7 @@ struct LocationSharingOptionButton: View { @Environment(\.theme) private var theme: ThemeSwiftUI let text: String - let action: () -> (Void) + let action: () -> Void @ViewBuilder var buttonIcon: Content var body: some View { @@ -44,21 +43,15 @@ struct LocationSharingOptionButton: View { struct LocationSharingOptionButton_Previews: PreviewProvider { static var previews: some View { VStack(alignment: .leading) { - LocationSharingOptionButton(text: VectorL10n.locationSharingStaticShareTitle) { - - } buttonIcon: { + LocationSharingOptionButton(text: VectorL10n.locationSharingStaticShareTitle) { } buttonIcon: { AvatarImage(avatarData: AvatarInput(mxContentUri: nil, matrixItemId: "Alice", displayName: "Alice"), size: .medium) .border() } - LocationSharingOptionButton(text: VectorL10n.locationSharingLiveShareTitle) { - - } buttonIcon: { + LocationSharingOptionButton(text: VectorL10n.locationSharingLiveShareTitle) { } buttonIcon: { Image(uiImage: Asset.Images.locationLiveIcon.image) .resizable() } - LocationSharingOptionButton(text: VectorL10n.locationSharingPinDropShareTitle) { - - } buttonIcon: { + LocationSharingOptionButton(text: VectorL10n.locationSharingPinDropShareTitle) { } buttonIcon: { Image(uiImage: Asset.Images.locationPinIcon.image) .resizable() } diff --git a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/View/LocationSharingView.swift b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/View/LocationSharingView.swift index 5f82bce08..a2acb30f1 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/View/LocationSharingView.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/View/LocationSharingView.swift @@ -14,11 +14,10 @@ // limitations under the License. // -import SwiftUI import CoreLocation +import SwiftUI struct LocationSharingView: View { - // MARK: - Properties // MARK: Private @@ -39,7 +38,6 @@ struct LocationSharingView: View { } VStack(spacing: 0) { - if context.viewState.showMapLoadingError { MapLoadingErrorView() } else { @@ -50,7 +48,7 @@ struct LocationSharingView: View { }) .padding(.bottom, 10.0) .actionSheet(isPresented: $context.showMapCreditsSheet) { - return MapCreditsActionSheet(openURL: { url in + MapCreditsActionSheet(openURL: { url in openURL(url) }).sheet } @@ -99,8 +97,8 @@ struct LocationSharingView: View { mapCenterCoordinate: $context.pinLocation, errorSubject: context.viewState.errorSubject, userDidPan: { - context.send(viewAction: .userDidPan) - }) + context.send(viewAction: .userDidPan) + }) if context.viewState.isPinDropSharing { LocationSharingMarkerView(backgroundColor: theme.colors.accent) { Image(uiImage: Asset.Images.locationPinIcon.image) diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Coordinator/StaticLocationViewingCoordinator.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Coordinator/StaticLocationViewingCoordinator.swift index 9475eeac8..b125fbcd6 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Coordinator/StaticLocationViewingCoordinator.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Coordinator/StaticLocationViewingCoordinator.swift @@ -15,9 +15,9 @@ // import Foundation -import UIKit -import SwiftUI import MatrixSDK +import SwiftUI +import UIKit struct StaticLocationViewingCoordinatorParameters { let session: MXSession @@ -28,7 +28,6 @@ struct StaticLocationViewingCoordinatorParameters { } final class StaticLocationViewingCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -54,7 +53,8 @@ final class StaticLocationViewingCoordinator: Coordinator, Presentable { mapStyleURL: parameters.session.vc_homeserverConfiguration().tileServer.mapStyleURL, avatarData: parameters.avatarData, location: parameters.location, - coordinateType: parameters.coordinateType) + coordinateType: parameters.coordinateType + ) let view = StaticLocationView(viewModel: viewModel.context) .addDependency(AvatarService.instantiate(mediaManager: parameters.mediaManager)) staticLocationViewingViewModel = viewModel @@ -62,6 +62,7 @@ final class StaticLocationViewingCoordinator: Coordinator, Presentable { } // MARK: - Public + func start() { MXLog.debug("[StaticLocationSharingViewerCoordinator] did start.") staticLocationViewingViewModel.completion = { [weak self] result in @@ -77,13 +78,12 @@ final class StaticLocationViewingCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.staticLocationViewingHostingController + staticLocationViewingHostingController } func presentLocationActivityController(with coordinate: CLLocationCoordinate2D) { - let shareActivityController = shareLocationActivityControllerBuilder.build(with: coordinate) - self.staticLocationViewingHostingController.present(shareActivityController, animated: true) + staticLocationViewingHostingController.present(shareActivityController, animated: true) } } diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/MockStaticLocationViewingScreenState.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/MockStaticLocationViewingScreenState.swift index 800057980..2ea0f0aec 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/MockStaticLocationViewingScreenState.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/MockStaticLocationViewingScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,9 +14,9 @@ // limitations under the License. // +import CoreLocation import Foundation import SwiftUI -import CoreLocation /// Using an enum for the screen allows you define the different state cases with /// the relevant associated data for each case. @@ -34,11 +34,11 @@ enum MockStaticLocationViewingScreenState: MockScreenState, CaseIterable { /// A list of screen state definitions static var allCases: [MockStaticLocationViewingScreenState] { - return [.showUserLocation, .showPinLocation] + [.showUserLocation, .showPinLocation] } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let location = CLLocationCoordinate2D(latitude: 51.4932641, longitude: -0.257096) let coordinateType: LocationSharingCoordinateType = self == .showUserLocation ? .user : .pin @@ -50,7 +50,6 @@ enum MockStaticLocationViewingScreenState: MockScreenState, CaseIterable { return ([viewModel], AnyView(StaticLocationView(viewModel: viewModel.context) - .addDependency(MockAvatarService.example)) - ) + .addDependency(MockAvatarService.example))) } } diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingModels.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingModels.swift index f51c26c63..f25b16e48 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingModels.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,9 +14,9 @@ // limitations under the License. // -import Foundation import Combine import CoreLocation +import Foundation // MARK: View model @@ -33,7 +33,6 @@ enum StaticLocationViewingViewModelResult { // MARK: View struct StaticLocationViewingViewState: BindableState { - /// Map style URL let mapStyleURL: URL @@ -43,7 +42,7 @@ struct StaticLocationViewingViewState: BindableState { /// Shared annotation to display existing location let sharedAnnotation: LocationAnnotation - var showLoadingIndicator: Bool = false + var showLoadingIndicator = false var shareButtonEnabled: Bool { !showLoadingIndicator diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift index 8564596a1..1f63e248e 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift @@ -14,14 +14,13 @@ // limitations under the License. // -import SwiftUI import CoreLocation +import SwiftUI typealias StaticLocationViewingViewModelType = StateStoreViewModel + Never, + StaticLocationViewingViewAction> class StaticLocationViewingViewModel: StaticLocationViewingViewModelType, StaticLocationViewingViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -79,7 +78,7 @@ class StaticLocationViewingViewModel: StaticLocationViewingViewModelType, Static switch error { case .invalidLocationAuthorization: - if let applicationSettingsURL = URL(string:UIApplication.openSettingsURLString) { + if let applicationSettingsURL = URL(string: UIApplication.openSettingsURLString) { UIApplication.shared.open(applicationSettingsURL) } else { self?.completion?(.close) diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModelProtocol.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModelProtocol.swift index 7835e5c4e..e61ed6b3a 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol StaticLocationViewingViewModelProtocol { - var completion: ((StaticLocationViewingViewModelResult) -> Void)? { get set } var context: StaticLocationViewingViewModelType.Context { get } } diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Test/UI/StaticLocationViewingUITests.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Test/UI/StaticLocationViewingUITests.swift index d6f26f247..ae2cea9aa 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Test/UI/StaticLocationViewingUITests.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Test/UI/StaticLocationViewingUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class StaticLocationViewingUITests: MockScreenTestCase { // This test has issues running consistently on CI. Removed for now until the issue has been fixed. diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Test/Unit/StaticLocationViewingViewModelTests.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Test/Unit/StaticLocationViewingViewModelTests.swift index 22ce395a7..f9aa24324 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Test/Unit/StaticLocationViewingViewModelTests.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Test/Unit/StaticLocationViewingViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,14 +14,13 @@ // limitations under the License. // -import XCTest import Combine import CoreLocation +import XCTest @testable import RiotSwiftUI class StaticLocationViewingViewModelTests: XCTestCase { - var cancellables = Set() func testInitialState() { @@ -39,7 +38,7 @@ class StaticLocationViewingViewModelTests: XCTestCase { func testCancellation() { let viewModel = buildViewModel() - let expectation = self.expectation(description: "Cancellation completion should be invoked") + let expectation = expectation(description: "Cancellation completion should be invoked") viewModel.completion = { result in switch result { @@ -58,7 +57,7 @@ class StaticLocationViewingViewModelTests: XCTestCase { func testShareExistingLocation() { let viewModel = buildViewModel() - let expectation = self.expectation(description: "Share completion should be invoked") + let expectation = expectation(description: "Share completion should be invoked") viewModel.completion = { result in switch result { diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/View/StaticLocationView.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/View/StaticLocationView.swift index 7436aa5e8..3b12fa00c 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/View/StaticLocationView.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/View/StaticLocationView.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct StaticLocationView: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Onboarding/Avatar/Coordinator/OnboardingAvatarCoordinator.swift b/RiotSwiftUI/Modules/Onboarding/Avatar/Coordinator/OnboardingAvatarCoordinator.swift index 216a65ea4..e3f3de9cd 100644 --- a/RiotSwiftUI/Modules/Onboarding/Avatar/Coordinator/OnboardingAvatarCoordinator.swift +++ b/RiotSwiftUI/Modules/Onboarding/Avatar/Coordinator/OnboardingAvatarCoordinator.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI struct OnboardingAvatarCoordinatorParameters { let userSession: UserSession @@ -32,7 +32,6 @@ enum OnboardingAvatarCoordinatorResult { } final class OnboardingAvatarCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -84,7 +83,6 @@ final class OnboardingAvatarCoordinator: Coordinator, Presentable { indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: onboardingAvatarHostingController) } - // MARK: - Public func start() { @@ -106,7 +104,7 @@ final class OnboardingAvatarCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.onboardingAvatarHostingController + onboardingAvatarHostingController } // MARK: - Private @@ -144,8 +142,8 @@ final class OnboardingAvatarCoordinator: Coordinator, Presentable { guard let avatarData = MXKTools.forceImageOrientationUp(image)?.jpegData(compressionQuality: 0.5) else { MXLog.error("[OnboardingAvatarCoordinator] Failed to create jpeg data.") - self.stopWaiting() - self.onboardingAvatarViewModel.processError(nil) + stopWaiting() + onboardingAvatarViewModel.processError(nil) return } diff --git a/RiotSwiftUI/Modules/Onboarding/Avatar/MockOnboardingAvatarScreenState.swift b/RiotSwiftUI/Modules/Onboarding/Avatar/MockOnboardingAvatarScreenState.swift index 7cf96984a..3c972b602 100644 --- a/RiotSwiftUI/Modules/Onboarding/Avatar/MockOnboardingAvatarScreenState.swift +++ b/RiotSwiftUI/Modules/Onboarding/Avatar/MockOnboardingAvatarScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,7 +43,7 @@ enum MockOnboardingAvatarScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let avatarColorCount = DefaultThemeSwiftUI().colors.namesAndAvatars.count let viewModel: OnboardingAvatarViewModel switch self { diff --git a/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarModels.swift b/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarModels.swift index 7f0187ebb..036498577 100644 --- a/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarModels.swift +++ b/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarViewModel.swift b/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarViewModel.swift index 7d1c23f5c..47bcf70a5 100644 --- a/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarViewModel.swift @@ -14,15 +14,14 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias OnboardingAvatarViewModelType = StateStoreViewModel + Never, + OnboardingAvatarViewAction> class OnboardingAvatarViewModel: OnboardingAvatarViewModelType, OnboardingAvatarViewModelProtocol { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarViewModelProtocol.swift b/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarViewModelProtocol.swift index 0c3eb58c8..bda30b06e 100644 --- a/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI protocol OnboardingAvatarViewModelProtocol { - var callback: ((OnboardingAvatarViewModelResult) -> Void)? { get set } var context: OnboardingAvatarViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Onboarding/Avatar/Test/UI/OnboardingAvatarUITests.swift b/RiotSwiftUI/Modules/Onboarding/Avatar/Test/UI/OnboardingAvatarUITests.swift index dc71847b5..13318da78 100644 --- a/RiotSwiftUI/Modules/Onboarding/Avatar/Test/UI/OnboardingAvatarUITests.swift +++ b/RiotSwiftUI/Modules/Onboarding/Avatar/Test/UI/OnboardingAvatarUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class OnboardingAvatarUITests: MockScreenTestCase { let userId = "@example:matrix.org" diff --git a/RiotSwiftUI/Modules/Onboarding/Avatar/Test/Unit/OnboardingAvatarViewModelTests.swift b/RiotSwiftUI/Modules/Onboarding/Avatar/Test/Unit/OnboardingAvatarViewModelTests.swift index fd0f284d3..228ae6c01 100644 --- a/RiotSwiftUI/Modules/Onboarding/Avatar/Test/Unit/OnboardingAvatarViewModelTests.swift +++ b/RiotSwiftUI/Modules/Onboarding/Avatar/Test/Unit/OnboardingAvatarViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI diff --git a/RiotSwiftUI/Modules/Onboarding/Avatar/View/OnboardingAvatarScreen.swift b/RiotSwiftUI/Modules/Onboarding/Avatar/View/OnboardingAvatarScreen.swift index dcb1af1dd..f93f4cfff 100644 --- a/RiotSwiftUI/Modules/Onboarding/Avatar/View/OnboardingAvatarScreen.swift +++ b/RiotSwiftUI/Modules/Onboarding/Avatar/View/OnboardingAvatarScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,11 +14,10 @@ // limitations under the License. // -import SwiftUI import DesignKit +import SwiftUI struct OnboardingAvatarScreen: View { - // MARK: - Properties // MARK: Private @@ -53,7 +52,6 @@ struct OnboardingAvatarScreen: View { .accentColor(theme.colors.accent) } - /// The user's avatar along with a picker button var avatar: some View { Group { diff --git a/RiotSwiftUI/Modules/Onboarding/Celebration/Coordinator/OnboardingCelebrationCoordinator.swift b/RiotSwiftUI/Modules/Onboarding/Celebration/Coordinator/OnboardingCelebrationCoordinator.swift index 5cfcd8d95..9f0ea9273 100644 --- a/RiotSwiftUI/Modules/Onboarding/Celebration/Coordinator/OnboardingCelebrationCoordinator.swift +++ b/RiotSwiftUI/Modules/Onboarding/Celebration/Coordinator/OnboardingCelebrationCoordinator.swift @@ -21,7 +21,6 @@ struct OnboardingCelebrationCoordinatorParameters { } final class OnboardingCelebrationCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -49,6 +48,7 @@ final class OnboardingCelebrationCoordinator: Coordinator, Presentable { } // MARK: - Public + func start() { MXLog.debug("[OnboardingCelebrationCoordinator] did start.") onboardingCelebrationViewModel.completion = { [weak self] in @@ -59,6 +59,6 @@ final class OnboardingCelebrationCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.onboardingCelebrationHostingController + onboardingCelebrationHostingController } } diff --git a/RiotSwiftUI/Modules/Onboarding/Celebration/MockOnboardingCelebrationScreenState.swift b/RiotSwiftUI/Modules/Onboarding/Celebration/MockOnboardingCelebrationScreenState.swift index 630fbe6bc..d911f8249 100644 --- a/RiotSwiftUI/Modules/Onboarding/Celebration/MockOnboardingCelebrationScreenState.swift +++ b/RiotSwiftUI/Modules/Onboarding/Celebration/MockOnboardingCelebrationScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,7 +31,7 @@ enum MockOnboardingCelebrationScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel = OnboardingCelebrationViewModel() // can simulate service and viewModel actions here if needs be. diff --git a/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationModels.swift b/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationModels.swift index 49549b9e1..5029fa7f0 100644 --- a/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationModels.swift +++ b/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationViewModel.swift b/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationViewModel.swift index 67bee880d..4bba32dea 100644 --- a/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationViewModel.swift @@ -17,11 +17,10 @@ import SwiftUI typealias OnboardingCelebrationViewModelType = StateStoreViewModel + Never, + OnboardingCelebrationViewAction> class OnboardingCelebrationViewModel: OnboardingCelebrationViewModelType, OnboardingCelebrationViewModelProtocol { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationViewModelProtocol.swift b/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationViewModelProtocol.swift index 7c16672b4..c3c5550f2 100644 --- a/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol OnboardingCelebrationViewModelProtocol { - var completion: (() -> Void)? { get set } var context: OnboardingCelebrationViewModelType.Context { get } } diff --git a/RiotSwiftUI/Modules/Onboarding/Celebration/Test/UI/OnboardingCelebrationUITests.swift b/RiotSwiftUI/Modules/Onboarding/Celebration/Test/UI/OnboardingCelebrationUITests.swift index 12d0f210a..587dfb576 100644 --- a/RiotSwiftUI/Modules/Onboarding/Celebration/Test/UI/OnboardingCelebrationUITests.swift +++ b/RiotSwiftUI/Modules/Onboarding/Celebration/Test/UI/OnboardingCelebrationUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class OnboardingCelebrationUITests: MockScreenTestCase { // Nothing to test as the view is completely static diff --git a/RiotSwiftUI/Modules/Onboarding/Celebration/Test/Unit/OnboardingCelebrationViewModelTests.swift b/RiotSwiftUI/Modules/Onboarding/Celebration/Test/Unit/OnboardingCelebrationViewModelTests.swift index 3874d1dd2..c512e319b 100644 --- a/RiotSwiftUI/Modules/Onboarding/Celebration/Test/Unit/OnboardingCelebrationViewModelTests.swift +++ b/RiotSwiftUI/Modules/Onboarding/Celebration/Test/Unit/OnboardingCelebrationViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Onboarding/Celebration/View/OnboardingCelebrationScreen.swift b/RiotSwiftUI/Modules/Onboarding/Celebration/View/OnboardingCelebrationScreen.swift index 1d6638114..2ebe7aa28 100644 --- a/RiotSwiftUI/Modules/Onboarding/Celebration/View/OnboardingCelebrationScreen.swift +++ b/RiotSwiftUI/Modules/Onboarding/Celebration/View/OnboardingCelebrationScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,11 +14,10 @@ // limitations under the License. // -import SwiftUI import SceneKit +import SwiftUI struct OnboardingCelebrationScreen: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Onboarding/Common/OnboardingBreakerScreenBackground.swift b/RiotSwiftUI/Modules/Onboarding/Common/OnboardingBreakerScreenBackground.swift index 39fd14915..d690ade43 100644 --- a/RiotSwiftUI/Modules/Onboarding/Common/OnboardingBreakerScreenBackground.swift +++ b/RiotSwiftUI/Modules/Onboarding/Common/OnboardingBreakerScreenBackground.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct OnboardingBreakerScreenBackground: View { - @Environment(\.theme) private var theme /// Flag indicating whether the gradient enabled on light theme @@ -43,21 +42,20 @@ struct OnboardingBreakerScreenBackground: View { ZStack(alignment: .top) { theme.colors.background - if isGradientEnabled && !theme.isDark { + if isGradientEnabled, !theme.isDark { LinearGradient(gradient: gradient, startPoint: .leading, endPoint: .trailing) - .opacity(0.3) - .mask(LinearGradient(colors: [.white, .clear], - startPoint: .top, - endPoint: .bottom)) - .frame(height: geometry.size.height * 0.65) + .opacity(0.3) + .mask(LinearGradient(colors: [.white, .clear], + startPoint: .top, + endPoint: .bottom)) + .frame(height: geometry.size.height * 0.65) } } } .ignoresSafeArea() } - } // MARK: - Previews diff --git a/RiotSwiftUI/Modules/Onboarding/Common/OnboardingButtonStyle.swift b/RiotSwiftUI/Modules/Onboarding/Common/OnboardingButtonStyle.swift index f0ba669dd..f4ee8a8ba 100644 --- a/RiotSwiftUI/Modules/Onboarding/Common/OnboardingButtonStyle.swift +++ b/RiotSwiftUI/Modules/Onboarding/Common/OnboardingButtonStyle.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Onboarding/Common/OnboardingIcon.swift b/RiotSwiftUI/Modules/Onboarding/Common/OnboardingIcon.swift index 598f07446..4993ce002 100644 --- a/RiotSwiftUI/Modules/Onboarding/Common/OnboardingIcon.swift +++ b/RiotSwiftUI/Modules/Onboarding/Common/OnboardingIcon.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct OnboardingIconImage: View { - @Environment(\.theme) private var theme let image: ImageAsset diff --git a/RiotSwiftUI/Modules/Onboarding/Common/OnboardingMetrics.swift b/RiotSwiftUI/Modules/Onboarding/Common/OnboardingMetrics.swift index 6eb697ed3..654d0e0f0 100644 --- a/RiotSwiftUI/Modules/Onboarding/Common/OnboardingMetrics.swift +++ b/RiotSwiftUI/Modules/Onboarding/Common/OnboardingMetrics.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Onboarding/Common/OnboardingTintedFullStopText.swift b/RiotSwiftUI/Modules/Onboarding/Common/OnboardingTintedFullStopText.swift index 5c6aa2ab3..d79484065 100644 --- a/RiotSwiftUI/Modules/Onboarding/Common/OnboardingTintedFullStopText.swift +++ b/RiotSwiftUI/Modules/Onboarding/Common/OnboardingTintedFullStopText.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,10 +19,10 @@ import SwiftUI /// A view that displays text, highlighting the first occurrence of /// the character `.` in the theme's accent color. struct OnboardingTintedFullStopText: View { - // MARK: - Properties // MARK: Private + @Environment(\.theme) private var theme private struct StringComponent { diff --git a/RiotSwiftUI/Modules/Onboarding/Congratulations/Coordinator/OnboardingCongratulationsCoordinator.swift b/RiotSwiftUI/Modules/Onboarding/Congratulations/Coordinator/OnboardingCongratulationsCoordinator.swift index 59cec36c4..57cd09d5c 100644 --- a/RiotSwiftUI/Modules/Onboarding/Congratulations/Coordinator/OnboardingCongratulationsCoordinator.swift +++ b/RiotSwiftUI/Modules/Onboarding/Congratulations/Coordinator/OnboardingCongratulationsCoordinator.swift @@ -32,7 +32,6 @@ enum OnboardingCongratulationsCoordinatorResult { } final class OnboardingCongratulationsCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -62,6 +61,7 @@ final class OnboardingCongratulationsCoordinator: Coordinator, Presentable { } // MARK: - Public + func start() { MXLog.debug("[OnboardingCongratulationsCoordinator] did start.") onboardingCongratulationsViewModel.completion = { [weak self] result in @@ -78,6 +78,6 @@ final class OnboardingCongratulationsCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.onboardingCongratulationsHostingController + onboardingCongratulationsHostingController } } diff --git a/RiotSwiftUI/Modules/Onboarding/Congratulations/MockOnboardingCongratulationsScreenState.swift b/RiotSwiftUI/Modules/Onboarding/Congratulations/MockOnboardingCongratulationsScreenState.swift index afe5fb92e..98e45e2c3 100644 --- a/RiotSwiftUI/Modules/Onboarding/Congratulations/MockOnboardingCongratulationsScreenState.swift +++ b/RiotSwiftUI/Modules/Onboarding/Congratulations/MockOnboardingCongratulationsScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,7 +32,7 @@ enum MockOnboardingCongratulationsScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel: OnboardingCongratulationsViewModel switch self { diff --git a/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsModels.swift b/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsModels.swift index bfc53c682..afe3f94f9 100644 --- a/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsModels.swift +++ b/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsViewModel.swift b/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsViewModel.swift index 5bb90c0d7..f61c3f4b0 100644 --- a/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsViewModel.swift @@ -17,11 +17,10 @@ import SwiftUI typealias OnboardingCongratulationsViewModelType = StateStoreViewModel + Never, + OnboardingCongratulationsViewAction> class OnboardingCongratulationsViewModel: OnboardingCongratulationsViewModelType, OnboardingCongratulationsViewModelProtocol { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsViewModelProtocol.swift b/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsViewModelProtocol.swift index 531e5614a..15e985922 100644 --- a/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol OnboardingCongratulationsViewModelProtocol { - var completion: ((OnboardingCongratulationsViewModelResult) -> Void)? { get set } var context: OnboardingCongratulationsViewModelType.Context { get } } diff --git a/RiotSwiftUI/Modules/Onboarding/Congratulations/Test/UI/OnboardingCongratulationsUITests.swift b/RiotSwiftUI/Modules/Onboarding/Congratulations/Test/UI/OnboardingCongratulationsUITests.swift index 9b4c8f9f6..ca32b079b 100644 --- a/RiotSwiftUI/Modules/Onboarding/Congratulations/Test/UI/OnboardingCongratulationsUITests.swift +++ b/RiotSwiftUI/Modules/Onboarding/Congratulations/Test/UI/OnboardingCongratulationsUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class OnboardingCongratulationsUITests: MockScreenTestCase { func testButtons() { diff --git a/RiotSwiftUI/Modules/Onboarding/Congratulations/Test/Unit/OnboardingCongratulationsViewModelTests.swift b/RiotSwiftUI/Modules/Onboarding/Congratulations/Test/Unit/OnboardingCongratulationsViewModelTests.swift index 8d46382e8..d46d1be32 100644 --- a/RiotSwiftUI/Modules/Onboarding/Congratulations/Test/Unit/OnboardingCongratulationsViewModelTests.swift +++ b/RiotSwiftUI/Modules/Onboarding/Congratulations/Test/Unit/OnboardingCongratulationsViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Onboarding/Congratulations/View/OnboardingCongratulationsScreen.swift b/RiotSwiftUI/Modules/Onboarding/Congratulations/View/OnboardingCongratulationsScreen.swift index d0ca409b0..a6f165920 100644 --- a/RiotSwiftUI/Modules/Onboarding/Congratulations/View/OnboardingCongratulationsScreen.swift +++ b/RiotSwiftUI/Modules/Onboarding/Congratulations/View/OnboardingCongratulationsScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct OnboardingCongratulationsScreen: View { - // MARK: - Properties // MARK: Private @@ -62,7 +61,7 @@ struct OnboardingCongratulationsScreen: View { .overlay(effects.ignoresSafeArea()) .background(theme.colors.accent.ignoresSafeArea()) .navigationBarHidden(true) - .preferredColorScheme(.dark) // make the status bar white + .preferredColorScheme(.dark) // make the status bar white .accentColor(.white) } diff --git a/RiotSwiftUI/Modules/Onboarding/DisplayName/Coordinator/OnboardingDisplayNameCoordinator.swift b/RiotSwiftUI/Modules/Onboarding/DisplayName/Coordinator/OnboardingDisplayNameCoordinator.swift index feeab4018..78837e8a2 100644 --- a/RiotSwiftUI/Modules/Onboarding/DisplayName/Coordinator/OnboardingDisplayNameCoordinator.swift +++ b/RiotSwiftUI/Modules/Onboarding/DisplayName/Coordinator/OnboardingDisplayNameCoordinator.swift @@ -14,15 +14,14 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI struct OnboardingDisplayNameCoordinatorParameters { let userSession: UserSession } final class OnboardingDisplayNameCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -58,6 +57,7 @@ final class OnboardingDisplayNameCoordinator: Coordinator, Presentable { } // MARK: - Public + func start() { MXLog.debug("[OnboardingDisplayNameCoordinator] did start.") onboardingDisplayNameViewModel.completion = { [weak self] result in @@ -74,7 +74,7 @@ final class OnboardingDisplayNameCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.onboardingDisplayNameHostingController + onboardingDisplayNameHostingController } // MARK: - Private diff --git a/RiotSwiftUI/Modules/Onboarding/DisplayName/MockOnboardingDisplayNameScreenState.swift b/RiotSwiftUI/Modules/Onboarding/DisplayName/MockOnboardingDisplayNameScreenState.swift index 0e50d478c..b9a027d31 100644 --- a/RiotSwiftUI/Modules/Onboarding/DisplayName/MockOnboardingDisplayNameScreenState.swift +++ b/RiotSwiftUI/Modules/Onboarding/DisplayName/MockOnboardingDisplayNameScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,7 +44,7 @@ enum MockOnboardingDisplayNameScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel: OnboardingDisplayNameViewModel switch self { case .emptyTextField: diff --git a/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameModels.swift b/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameModels.swift index 22857f69c..bae11189e 100644 --- a/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameModels.swift +++ b/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameViewModel.swift b/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameViewModel.swift index 547a71b22..cfc7998f2 100644 --- a/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameViewModel.swift @@ -14,15 +14,14 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias OnboardingDisplayNameViewModelType = StateStoreViewModel + Never, + OnboardingDisplayNameViewAction> class OnboardingDisplayNameViewModel: OnboardingDisplayNameViewModelType, OnboardingDisplayNameViewModelProtocol { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameViewModelProtocol.swift b/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameViewModelProtocol.swift index 7380bee9a..ae768a816 100644 --- a/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol OnboardingDisplayNameViewModelProtocol { - var completion: ((OnboardingDisplayNameViewModelResult) -> Void)? { get set } var context: OnboardingDisplayNameViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Onboarding/DisplayName/Test/UI/OnboardingDisplayNameUITests.swift b/RiotSwiftUI/Modules/Onboarding/DisplayName/Test/UI/OnboardingDisplayNameUITests.swift index 6c27e09de..9c006dc6b 100644 --- a/RiotSwiftUI/Modules/Onboarding/DisplayName/Test/UI/OnboardingDisplayNameUITests.swift +++ b/RiotSwiftUI/Modules/Onboarding/DisplayName/Test/UI/OnboardingDisplayNameUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class OnboardingDisplayNameUITests: MockScreenTestCase { func testEmptyTextField() { @@ -55,8 +55,8 @@ class OnboardingDisplayNameUITests: MockScreenTestCase { func testLongDisplayName() { let displayName = """ - Bacon ipsum dolor amet filet mignon chicken kevin andouille. Doner shoulder beef, brisket bresaola turkey jowl venison. Ham hock cow turducken, chislic venison doner short loin strip steak tri-tip jowl. Sirloin pork belly hamburger ribeye. Tail capicola alcatra short ribs turkey doner. - """ + Bacon ipsum dolor amet filet mignon chicken kevin andouille. Doner shoulder beef, brisket bresaola turkey jowl venison. Ham hock cow turducken, chislic venison doner short loin strip steak tri-tip jowl. Sirloin pork belly hamburger ribeye. Tail capicola alcatra short ribs turkey doner. + """ app.goToScreenWithIdentifier(MockOnboardingDisplayNameScreenState.longDisplayName(displayName: displayName).title) let textField = app.textFields.element diff --git a/RiotSwiftUI/Modules/Onboarding/DisplayName/Test/Unit/OnboardingDisplayNameViewModelTests.swift b/RiotSwiftUI/Modules/Onboarding/DisplayName/Test/Unit/OnboardingDisplayNameViewModelTests.swift index c9b72f045..2dbc015e3 100644 --- a/RiotSwiftUI/Modules/Onboarding/DisplayName/Test/Unit/OnboardingDisplayNameViewModelTests.swift +++ b/RiotSwiftUI/Modules/Onboarding/DisplayName/Test/Unit/OnboardingDisplayNameViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI diff --git a/RiotSwiftUI/Modules/Onboarding/DisplayName/View/OnboardingDisplayNameScreen.swift b/RiotSwiftUI/Modules/Onboarding/DisplayName/View/OnboardingDisplayNameScreen.swift index 005a33f2c..09fc1ac10 100644 --- a/RiotSwiftUI/Modules/Onboarding/DisplayName/View/OnboardingDisplayNameScreen.swift +++ b/RiotSwiftUI/Modules/Onboarding/DisplayName/View/OnboardingDisplayNameScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct OnboardingDisplayNameScreen: View { - // MARK: - Properties // MARK: Private @@ -64,7 +63,6 @@ struct OnboardingDisplayNameScreen: View { /// The icon, title and message views. var header: some View { VStack(spacing: 8) { - OnboardingIconImage(image: Asset.Images.onboardingCongratulationsIcon) .padding(.bottom, 8) diff --git a/RiotSwiftUI/Modules/Onboarding/SplashScreen/Coordinator/OnboardingSplashScreenCoordinator.swift b/RiotSwiftUI/Modules/Onboarding/SplashScreen/Coordinator/OnboardingSplashScreenCoordinator.swift index 534bb4e64..27c4b3dbc 100644 --- a/RiotSwiftUI/Modules/Onboarding/SplashScreen/Coordinator/OnboardingSplashScreenCoordinator.swift +++ b/RiotSwiftUI/Modules/Onboarding/SplashScreen/Coordinator/OnboardingSplashScreenCoordinator.swift @@ -14,15 +14,14 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI protocol OnboardingSplashScreenCoordinatorProtocol: Coordinator, Presentable { var completion: ((OnboardingSplashScreenViewModelResult) -> Void)? { get set } } final class OnboardingSplashScreenCoordinator: OnboardingSplashScreenCoordinatorProtocol { - // MARK: - Properties // MARK: Private @@ -52,6 +51,7 @@ final class OnboardingSplashScreenCoordinator: OnboardingSplashScreenCoordinator } // MARK: - Public + func start() { MXLog.debug("[OnboardingSplashScreenCoordinator] did start.") onboardingSplashScreenViewModel.completion = { [weak self] result in @@ -68,7 +68,7 @@ final class OnboardingSplashScreenCoordinator: OnboardingSplashScreenCoordinator } func toPresentable() -> UIViewController { - return onboardingSplashScreenHostingController + onboardingSplashScreenHostingController } /// Stops any ongoing activities in the coordinator. diff --git a/RiotSwiftUI/Modules/Onboarding/SplashScreen/MockOnboardingSplashScreenScreenState.swift b/RiotSwiftUI/Modules/Onboarding/SplashScreen/MockOnboardingSplashScreenScreenState.swift index 511e7cb6e..6e823719a 100644 --- a/RiotSwiftUI/Modules/Onboarding/SplashScreen/MockOnboardingSplashScreenScreenState.swift +++ b/RiotSwiftUI/Modules/Onboarding/SplashScreen/MockOnboardingSplashScreenScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,7 +36,7 @@ enum MockOnboardingSplashScreenScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel = OnboardingSplashScreenViewModel() // can simulate service and viewModel actions here if needs be. diff --git a/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenModels.swift b/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenModels.swift index 6bd22223f..3b81de78e 100644 --- a/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenModels.swift +++ b/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,7 +36,6 @@ enum OnboardingSplashScreenViewModelResult { // MARK: View struct OnboardingSplashScreenViewState: BindableState, CustomDebugStringConvertible { - /// The colours of the background gradient shown behind the 4 pages. private let gradientColors = [ Color(red: 0.95, green: 0.98, blue: 0.96), @@ -67,7 +66,7 @@ struct OnboardingSplashScreenViewState: BindableState, CustomDebugStringConverti let locale = Locale.current let page4Title = locale.identifier.hasPrefix("en") ? "Cut the slack from teams." : VectorL10n.onboardingSplashPage4TitleNoPun - self.content = [ + content = [ OnboardingSplashScreenPageContent(title: VectorL10n.onboardingSplashPage1Title, message: VectorL10n.onboardingSplashPage1Message, image: Asset.Images.onboardingSplashScreenPage1, @@ -83,9 +82,9 @@ struct OnboardingSplashScreenViewState: BindableState, CustomDebugStringConverti OnboardingSplashScreenPageContent(title: page4Title, message: VectorL10n.onboardingSplashPage4Message, image: Asset.Images.onboardingSplashScreenPage4, - darkImage: Asset.Images.onboardingSplashScreenPage4Dark), + darkImage: Asset.Images.onboardingSplashScreenPage4Dark) ] - self.bindings = OnboardingSplashScreenBindings() + bindings = OnboardingSplashScreenBindings() } } diff --git a/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenViewModel.swift b/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenViewModel.swift index 222f70f60..6d1c936ea 100644 --- a/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenViewModel.swift @@ -14,12 +14,12 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias OnboardingSplashScreenViewModelType = StateStoreViewModel + Never, + OnboardingSplashScreenViewAction> protocol OnboardingSplashScreenViewModelProtocol { var completion: ((OnboardingSplashScreenViewModelResult) -> Void)? { get set } @@ -27,7 +27,6 @@ protocol OnboardingSplashScreenViewModelProtocol { } class OnboardingSplashScreenViewModel: OnboardingSplashScreenViewModelType, OnboardingSplashScreenViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -55,7 +54,7 @@ class OnboardingSplashScreenViewModel: OnboardingSplashScreenViewModelType, Onbo state.bindings.pageIndex = (state.bindings.pageIndex + 1) % state.content.count case .previousPage: // Prevent the hidden page at index -1 from being shown. - state.bindings.pageIndex = max(0, (state.bindings.pageIndex - 1)) + state.bindings.pageIndex = max(0, state.bindings.pageIndex - 1) case .hiddenPage: // Hidden page for a nicer animation when looping back to the start. state.bindings.pageIndex = -1 diff --git a/RiotSwiftUI/Modules/Onboarding/SplashScreen/Test/Unit/OnboardingSplashScreenViewModelTests.swift b/RiotSwiftUI/Modules/Onboarding/SplashScreen/Test/Unit/OnboardingSplashScreenViewModelTests.swift index 4b47e0501..55b19703b 100644 --- a/RiotSwiftUI/Modules/Onboarding/SplashScreen/Test/Unit/OnboardingSplashScreenViewModelTests.swift +++ b/RiotSwiftUI/Modules/Onboarding/SplashScreen/Test/Unit/OnboardingSplashScreenViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,11 +14,9 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI -class OnboardingSplashScreenViewModelTests: XCTestCase { - -} +class OnboardingSplashScreenViewModelTests: XCTestCase { } diff --git a/RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreen.swift b/RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreen.swift index 6afe46726..b34239882 100644 --- a/RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreen.swift +++ b/RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import SwiftUI /// The splash screen shown at the beginning of the onboarding flow. struct OnboardingSplashScreen: View { - // MARK: - Properties // MARK: Private @@ -46,7 +45,6 @@ struct OnboardingSplashScreen: View { // The main content of the carousel HStack(alignment: .top, spacing: 0) { - // Add a hidden page at the start of the carousel duplicating the content of the last page OnboardingSplashScreenPage(content: viewModel.viewState.content[pageCount - 1]) .frame(width: geometry.size.width) @@ -55,7 +53,6 @@ struct OnboardingSplashScreen: View { OnboardingSplashScreenPage(content: viewModel.viewState.content[index]) .frame(width: geometry.size.width) } - } .offset(x: pageOffset(in: geometry)) @@ -63,8 +60,8 @@ struct OnboardingSplashScreen: View { OnboardingSplashScreenPageIndicator(pageCount: pageCount, pageIndex: viewModel.pageIndex) - .frame(width: geometry.size.width) - .padding(.bottom) + .frame(width: geometry.size.width) + .padding(.bottom) Spacer() @@ -130,7 +127,7 @@ struct OnboardingSplashScreen: View { private func startTimer() { guard pageTimer == nil else { return } - pageTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { timer in + pageTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { _ in if viewModel.pageIndex == pageCount - 1 { viewModel.send(viewAction: .hiddenPage) @@ -164,7 +161,7 @@ struct OnboardingSplashScreen: View { /// - Parameter width: The gesture's translation width. /// - Returns: `true` if there is another page to drag to. private func shouldSwipeForTranslation(_ width: CGFloat) -> Bool { - if viewModel.pageIndex == 0 { + if viewModel.pageIndex == 0 { return isLeftToRight ? width < 0 : width > 0 } else if viewModel.pageIndex == pageCount - 1 { return isLeftToRight ? width > 0 : width < 0 diff --git a/RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreenPage.swift b/RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreenPage.swift index e8e66d773..4047580d9 100644 --- a/RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreenPage.swift +++ b/RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreenPage.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,13 +17,14 @@ import SwiftUI struct OnboardingSplashScreenPage: View { - // MARK: - Properties // MARK: Private + @Environment(\.theme) private var theme // MARK: Public + /// The content that this page should display. let content: OnboardingSplashScreenPageContent @@ -35,8 +36,8 @@ struct OnboardingSplashScreenPage: View { .resizable() .scaledToFit() .frame(maxWidth: 310) // This value is problematic. 300 results in dropped frames - // on iPhone 12/13 Mini. 305 the same on iPhone 12/13. As of - // iOS 15, 310 seems fine on all supported screen widths 🤞. + // on iPhone 12/13 Mini. 305 the same on iPhone 12/13. As of + // iOS 15, 310 seems fine on all supported screen widths 🤞. .padding(20) .accessibilityHidden(true) @@ -60,7 +61,7 @@ struct OnboardingSplashScreenPage: View { struct OnboardingSplashScreenPage_Previews: PreviewProvider { static let content = OnboardingSplashScreenViewState().content static var previews: some View { - ForEach(0.. UIViewController { - return self.onboardingUseCaseHostingController + onboardingUseCaseHostingController } /// Stops any ongoing activities in the coordinator. diff --git a/RiotSwiftUI/Modules/Onboarding/UseCase/MockOnboardingUseCaseScreenState.swift b/RiotSwiftUI/Modules/Onboarding/UseCase/MockOnboardingUseCaseScreenState.swift index 4eb90c305..ed580b834 100644 --- a/RiotSwiftUI/Modules/Onboarding/UseCase/MockOnboardingUseCaseScreenState.swift +++ b/RiotSwiftUI/Modules/Onboarding/UseCase/MockOnboardingUseCaseScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,7 +37,7 @@ enum MockOnboardingUseCaseSelectionScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel = OnboardingUseCaseViewModel() // can simulate service and viewModel actions here if needs be. diff --git a/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseModels.swift b/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseModels.swift index a7160ee67..8ee4f408b 100644 --- a/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseModels.swift +++ b/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseViewModel.swift b/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseViewModel.swift index 4d0cc3135..b96c35ca2 100644 --- a/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseViewModel.swift @@ -17,11 +17,10 @@ import SwiftUI typealias OnboardingUseCaseViewModelType = StateStoreViewModel + OnboardingUseCaseStateAction, + OnboardingUseCaseViewAction> class OnboardingUseCaseViewModel: OnboardingUseCaseViewModelType, OnboardingUseCaseViewModelProtocol { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseViewModelProtocol.swift b/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseViewModelProtocol.swift index 44d56cfba..f9a89e4eb 100644 --- a/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol OnboardingUseCaseViewModelProtocol { - var completion: ((OnboardingUseCaseViewModelResult) -> Void)? { get set } var context: OnboardingUseCaseViewModelType.Context { get } } diff --git a/RiotSwiftUI/Modules/Onboarding/UseCase/Test/UI/OnboardingUseCaseUITests.swift b/RiotSwiftUI/Modules/Onboarding/UseCase/Test/UI/OnboardingUseCaseUITests.swift index fbb0a9a64..375c93865 100644 --- a/RiotSwiftUI/Modules/Onboarding/UseCase/Test/UI/OnboardingUseCaseUITests.swift +++ b/RiotSwiftUI/Modules/Onboarding/UseCase/Test/UI/OnboardingUseCaseUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class OnboardingUseCaseUITests: MockScreenTestCase { // The view has no parameters or changing state to test. diff --git a/RiotSwiftUI/Modules/Onboarding/UseCase/Test/Unit/OnboardingUseCaseViewModelTests.swift b/RiotSwiftUI/Modules/Onboarding/UseCase/Test/Unit/OnboardingUseCaseViewModelTests.swift index 9f5fa2b8f..eed714cf5 100644 --- a/RiotSwiftUI/Modules/Onboarding/UseCase/Test/Unit/OnboardingUseCaseViewModelTests.swift +++ b/RiotSwiftUI/Modules/Onboarding/UseCase/Test/Unit/OnboardingUseCaseViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Onboarding/UseCase/View/OnboardingUseCaseButton.swift b/RiotSwiftUI/Modules/Onboarding/UseCase/View/OnboardingUseCaseButton.swift index 7916fe280..bbbd9999c 100644 --- a/RiotSwiftUI/Modules/Onboarding/UseCase/View/OnboardingUseCaseButton.swift +++ b/RiotSwiftUI/Modules/Onboarding/UseCase/View/OnboardingUseCaseButton.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import SwiftUI /// A button used for the Use Case selection. struct OnboardingUseCaseButton: View { - // MARK: Private @Environment(\.theme) private var theme diff --git a/RiotSwiftUI/Modules/Onboarding/UseCase/View/OnboardingUseCaseSelectionScreen.swift b/RiotSwiftUI/Modules/Onboarding/UseCase/View/OnboardingUseCaseSelectionScreen.swift index f0fbbfa61..c4761aa47 100644 --- a/RiotSwiftUI/Modules/Onboarding/UseCase/View/OnboardingUseCaseSelectionScreen.swift +++ b/RiotSwiftUI/Modules/Onboarding/UseCase/View/OnboardingUseCaseSelectionScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import SwiftUI /// The screen shown to a new user to select their use case for the app. struct OnboardingUseCaseSelectionScreen: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingModels.swift b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingModels.swift index c33638ddc..76beb1205 100644 --- a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingModels.swift +++ b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingViewModel.swift b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingViewModel.swift index 75712d550..1e8111a7a 100644 --- a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingViewModel.swift +++ b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingViewModel.swift @@ -14,15 +14,14 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias AllChatsOnboardingViewModelType = StateStoreViewModel + Never, + AllChatsOnboardingViewAction> class AllChatsOnboardingViewModel: AllChatsOnboardingViewModelType, AllChatsOnboardingViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -34,7 +33,7 @@ class AllChatsOnboardingViewModel: AllChatsOnboardingViewModelType, AllChatsOnbo // MARK: - Setup static func makeAllChatsOnboardingViewModel() -> AllChatsOnboardingViewModelProtocol { - return AllChatsOnboardingViewModel() + AllChatsOnboardingViewModel() } private init() { @@ -42,7 +41,7 @@ class AllChatsOnboardingViewModel: AllChatsOnboardingViewModelType, AllChatsOnbo } private static func defaultState() -> AllChatsOnboardingViewState { - return AllChatsOnboardingViewState(pages: [ + AllChatsOnboardingViewState(pages: [ AllChatsOnboardingPageData(image: Asset.Images.allChatsOnboarding1.image, title: VectorL10n.allChatsOnboardingPageTitle1, message: VectorL10n.allChatsOnboardingPageMessage1), diff --git a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingViewModelProtocol.swift b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingViewModelProtocol.swift index a0cc2137d..dd963c407 100644 --- a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol AllChatsOnboardingViewModelProtocol { - var completion: ((AllChatsOnboardingViewModelResult) -> Void)? { get set } static func makeAllChatsOnboardingViewModel() -> AllChatsOnboardingViewModelProtocol var context: AllChatsOnboardingViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/Coordinator/AllChatsOnboardingCoordinator.swift b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/Coordinator/AllChatsOnboardingCoordinator.swift index de793e30e..df189c144 100644 --- a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/Coordinator/AllChatsOnboardingCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/Coordinator/AllChatsOnboardingCoordinator.swift @@ -14,12 +14,11 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI /// All Chats onboarding screen final class AllChatsOnboardingCoordinator: NSObject, Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -42,8 +41,8 @@ final class AllChatsOnboardingCoordinator: NSObject, Coordinator, Presentable { let viewModel = AllChatsOnboardingViewModel.makeAllChatsOnboardingViewModel() let view = AllChatsOnboarding(viewModel: viewModel.context) self.viewModel = viewModel - self.hostingController = VectorHostingController(rootView: view) - self.indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: hostingController) + hostingController = VectorHostingController(rootView: view) + indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: hostingController) super.init() @@ -65,7 +64,7 @@ final class AllChatsOnboardingCoordinator: NSObject, Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.hostingController + hostingController } // MARK: - Private @@ -87,9 +86,7 @@ final class AllChatsOnboardingCoordinator: NSObject, Coordinator, Presentable { // MARK: - UIAdaptivePresentationControllerDelegate extension AllChatsOnboardingCoordinator: UIAdaptivePresentationControllerDelegate { - func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { completion?() } - } diff --git a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/Coordinator/AllChatsOnboardingCoordinatorBridgePresenter.swift b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/Coordinator/AllChatsOnboardingCoordinatorBridgePresenter.swift index b367175ef..75977054d 100644 --- a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/Coordinator/AllChatsOnboardingCoordinatorBridgePresenter.swift +++ b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/Coordinator/AllChatsOnboardingCoordinatorBridgePresenter.swift @@ -26,7 +26,6 @@ import Foundation /// Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator. @objcMembers final class AllChatsOnboardingCoordinatorBridgePresenter: NSObject { - // MARK: - Properties // MARK: Private @@ -53,7 +52,7 @@ final class AllChatsOnboardingCoordinatorBridgePresenter: NSObject { } func dismiss(animated: Bool, completion: (() -> Void)?) { - guard let coordinator = self.coordinator else { + guard let coordinator = coordinator else { return } coordinator.toPresentable().dismiss(animated: animated) { @@ -62,4 +61,3 @@ final class AllChatsOnboardingCoordinatorBridgePresenter: NSObject { } } } - diff --git a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/View/AllChatsOnboarding.swift b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/View/AllChatsOnboarding.swift index b13f9a287..513cc55a3 100644 --- a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/View/AllChatsOnboarding.swift +++ b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/View/AllChatsOnboarding.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct AllChatsOnboarding: View { - // MARK: - Properties // MARK: Private @@ -36,12 +35,12 @@ struct AllChatsOnboarding: View { .foregroundColor(theme.colors.primaryContent) .padding() TabView(selection: $selectedTab) { - ForEach(viewModel.viewState.pages.indices) { index in + ForEach(viewModel.viewState.pages.indices, id: \.self) { index in let page = viewModel.viewState.pages[index] AllChatsOnboardingPage(image: page.image, title: page.title, message: page.message) - .tag(index) + .tag(index) } } .tabViewStyle(PageTabViewStyle(indexDisplayMode: .automatic)) @@ -61,7 +60,7 @@ struct AllChatsOnboarding: View { // MARK: - Private private func onCallToAction() { - if (selectedTab == viewModel.viewState.pages.count - 1) { + if selectedTab == viewModel.viewState.pages.count - 1 { viewModel.send(viewAction: .cancel) } else { withAnimation { diff --git a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/View/AllChatsOnboardingPage.swift b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/View/AllChatsOnboardingPage.swift index 04725ccae..c6a5f06fa 100644 --- a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/View/AllChatsOnboardingPage.swift +++ b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/View/AllChatsOnboardingPage.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct AllChatsOnboardingPage: View { - // MARK: - Properties let image: UIImage @@ -55,7 +54,7 @@ struct AllChatsOnboardingPage_Previews: PreviewProvider { preview.theme(.dark).preferredColorScheme(.dark) } - static private var preview: some View { + private static var preview: some View { AllChatsOnboardingPage(image: Asset.Images.allChatsOnboarding1.image, title: VectorL10n.allChatsOnboardingPageTitle1, message: VectorL10n.allChatsOnboardingPageMessage1) diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/Coordinator/RoomNotificationSettingsBridgePresenter.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/Coordinator/RoomNotificationSettingsBridgePresenter.swift index 0be2f18a1..be7571107 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/Coordinator/RoomNotificationSettingsBridgePresenter.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/Coordinator/RoomNotificationSettingsBridgePresenter.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +25,6 @@ import Foundation /// Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator. @objcMembers final class RoomNotificationSettingsCoordinatorBridgePresenter: NSObject { - // MARK: - Properties // MARK: Private @@ -61,11 +60,11 @@ final class RoomNotificationSettingsCoordinatorBridgePresenter: NSObject { viewController.present(navigationController, animated: animated, completion: nil) roomNotificationSettingsCoordinator.start() - self.coordinator = roomNotificationSettingsCoordinator + coordinator = roomNotificationSettingsCoordinator } func dismiss(animated: Bool, completion: (() -> Void)?) { - guard let coordinator = self.coordinator else { + guard let coordinator = coordinator else { return } coordinator.toPresentable().dismiss(animated: animated) { @@ -79,22 +78,21 @@ final class RoomNotificationSettingsCoordinatorBridgePresenter: NSObject { } // MARK: - RoomNotificationSettingsCoordinatorDelegate + extension RoomNotificationSettingsCoordinatorBridgePresenter: RoomNotificationSettingsCoordinatorDelegate { func roomNotificationSettingsCoordinatorDidCancel(_ coordinator: RoomNotificationSettingsCoordinatorType) { - self.delegate?.roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self) + delegate?.roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self) } func roomNotificationSettingsCoordinatorDidComplete(_ coordinator: RoomNotificationSettingsCoordinatorType) { - self.delegate?.roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self) + delegate?.roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self) } } // MARK: - UIAdaptivePresentationControllerDelegate extension RoomNotificationSettingsCoordinatorBridgePresenter: UIAdaptivePresentationControllerDelegate { - func roomNotificationSettingsCoordinatorDidComplete(_ presentationController: UIPresentationController) { - self.delegate?.roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self) + delegate?.roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self) } - } diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/Coordinator/RoomNotificationSettingsCoordinator.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/Coordinator/RoomNotificationSettingsCoordinator.swift index fef123281..e0d909898 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/Coordinator/RoomNotificationSettingsCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/Coordinator/RoomNotificationSettingsCoordinator.swift @@ -15,14 +15,14 @@ // import Foundation -import UIKit import SwiftUI +import UIKit final class RoomNotificationSettingsCoordinator: RoomNotificationSettingsCoordinatorType { - // MARK: - Properties // MARK: Private + private var roomNotificationSettingsViewModel: RoomNotificationSettingsViewModelType private let roomNotificationSettingsViewController: UIViewController @@ -47,34 +47,35 @@ final class RoomNotificationSettingsCoordinator: RoomNotificationSettingsCoordin roomNotificationService: roomNotificationService, avatarData: avatarData, displayName: room.summary.displayname, - roomEncrypted: room.summary.isEncrypted) + roomEncrypted: room.summary.isEncrypted + ) let avatarService: AvatarServiceProtocol = AvatarService(mediaManager: room.mxSession.mediaManager) let view = RoomNotificationSettings(viewModel: viewModel, presentedModally: presentedModally) .addDependency(avatarService) let viewController = VectorHostingController(rootView: view) - self.roomNotificationSettingsViewModel = viewModel - self.roomNotificationSettingsViewController = viewController + roomNotificationSettingsViewModel = viewModel + roomNotificationSettingsViewController = viewController } // MARK: - Public methods - func start() { - self.roomNotificationSettingsViewModel.coordinatorDelegate = self + func start() { + roomNotificationSettingsViewModel.coordinatorDelegate = self } func toPresentable() -> UIViewController { - return self.roomNotificationSettingsViewController + roomNotificationSettingsViewController } } // MARK: - RoomNotificationSettingsViewModelCoordinatorDelegate + extension RoomNotificationSettingsCoordinator: RoomNotificationSettingsViewModelCoordinatorDelegate { - func roomNotificationSettingsViewModelDidComplete(_ viewModel: RoomNotificationSettingsViewModelType) { - self.delegate?.roomNotificationSettingsCoordinatorDidComplete(self) + delegate?.roomNotificationSettingsCoordinatorDidComplete(self) } func roomNotificationSettingsViewModelDidCancel(_ viewModel: RoomNotificationSettingsViewModelType) { - self.delegate?.roomNotificationSettingsCoordinatorDidCancel(self) + delegate?.roomNotificationSettingsCoordinatorDidCancel(self) } } diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/Model/RoomNotificationSettingsViewState.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/Model/RoomNotificationSettingsViewState.swift index ba8e92916..96773de68 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/Model/RoomNotificationSettingsViewState.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/Model/RoomNotificationSettingsViewState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,4 +40,3 @@ extension RoomNotificationSettingsViewState { roomEncrypted ? VectorL10n.roomNotifsSettingsEncryptedRoomNotice : "" } } - diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/Model/RoomNotificationSettingsViewStateType.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/Model/RoomNotificationSettingsViewStateType.swift index 0b9c7e0b6..d17fcc3dc 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/Model/RoomNotificationSettingsViewStateType.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/Model/RoomNotificationSettingsViewStateType.swift @@ -26,4 +26,3 @@ protocol RoomNotificationSettingsViewStateType { var avatarData: AvatarProtocol? { get } var displayName: String? { get } } - diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/Model/RoomNotificationState.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/Model/RoomNotificationState.swift index 00c51d6fc..e34bb5faa 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/Model/RoomNotificationState.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/Model/RoomNotificationState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +25,7 @@ enum RoomNotificationState: Int { extension RoomNotificationState: CaseIterable { } extension RoomNotificationState: Identifiable { - var id: Int { self.rawValue } + var id: Int { rawValue } } extension RoomNotificationState { diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/Service/MatrixSDK/MXRoomNotificationSettingsService.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/Service/MatrixSDK/MXRoomNotificationSettingsService.swift index 03b868a83..e9d192b49 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/Service/MatrixSDK/MXRoomNotificationSettingsService.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/Service/MatrixSDK/MXRoomNotificationSettingsService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation final class MXRoomNotificationSettingsService: RoomNotificationSettingsServiceType { - typealias Completion = () -> Void // MARK: - Properties @@ -50,11 +49,11 @@ final class MXRoomNotificationSettingsService: RoomNotificationSettingsServiceTy // MARK: - Public func observeNotificationState(listener: @escaping RoomNotificationStateCallback) { - let observer = NotificationCenter.default.addObserver( forName: NSNotification.Name(rawValue: kMXNotificationCenterDidUpdateRules), object: nil, - queue: OperationQueue.main) { [weak self] _ in + queue: OperationQueue.main + ) { [weak self] _ in guard let self = self else { return } listener(self.room.notificationState) } @@ -88,7 +87,7 @@ final class MXRoomNotificationSettingsService: RoomNotificationSettingsServiceTy } guard let rule = room.overridePushRule else { - self.addPushRuleToMute(completion: completion) + addPushRuleToMute(completion: completion) return } @@ -96,7 +95,7 @@ final class MXRoomNotificationSettingsService: RoomNotificationSettingsServiceTy MXLog.debug("[RoomNotificationSettingsService] Request in progress: ignore push rule update") completion() return - } + } // if the user defined one, use it if rule.actionsContains(actionType: MXPushRuleActionTypeDontNotify) { @@ -130,7 +129,7 @@ final class MXRoomNotificationSettingsService: RoomNotificationSettingsServiceTy MXLog.debug("[MXRoom+Riot] Request in progress: ignore push rule update") completion() return - } + } // if the user defined one, use it if rule.actionsContains(actionType: MXPushRuleActionTypeDontNotify) { @@ -140,11 +139,10 @@ final class MXRoomNotificationSettingsService: RoomNotificationSettingsServiceTy self.addPushRuleToMentionOnly(completion: completion) } } - } private func allMessages(completion: @escaping Completion) { - if !room.isMentionsOnly && !room.isMuted { + if !room.isMentionsOnly, !room.isMuted { completion() return } @@ -172,7 +170,8 @@ final class MXRoomNotificationSettingsService: RoomNotificationSettingsServiceTy room.roomId, notify: false, sound: false, - highlight: false) + highlight: false + ) } private func addPushRuleToMute(completion: @escaping Completion) { @@ -207,18 +206,19 @@ final class MXRoomNotificationSettingsService: RoomNotificationSettingsServiceTy private func enablePushRule(rule: MXPushRule, completion: @escaping Completion) { handleUpdateCallback(completion) { // No way to check whether this notification concerns the push rule. Consider the change is applied. - return true + true } handleFailureCallback(completion) room.mxSession.notificationCenter.enableRule(rule, isEnabled: true) } - private func handleUpdateCallback(_ completion: @escaping Completion, releaseCheck: @escaping () -> Bool) { + private func handleUpdateCallback(_ completion: @escaping Completion, releaseCheck: @escaping () -> Bool) { notificationCenterDidUpdateObserver = NotificationCenter.default.addObserver( forName: NSNotification.Name(rawValue: kMXNotificationCenterDidUpdateRules), object: nil, - queue: OperationQueue.main) { [weak self] _ in + queue: OperationQueue.main + ) { [weak self] _ in guard let self = self else { return } if releaseCheck() { self.removeObservers() @@ -231,7 +231,8 @@ final class MXRoomNotificationSettingsService: RoomNotificationSettingsServiceTy notificationCenterDidFailObserver = NotificationCenter.default.addObserver( forName: NSNotification.Name(rawValue: kMXNotificationCenterDidFailRulesUpdate), object: nil, - queue: OperationQueue.main) { [weak self] _ in + queue: OperationQueue.main + ) { [weak self] _ in guard let self = self else { return } self.removeObservers() completion() @@ -239,23 +240,23 @@ final class MXRoomNotificationSettingsService: RoomNotificationSettingsServiceTy } func removeObservers() { - if let observer = self.notificationCenterDidUpdateObserver { + if let observer = notificationCenterDidUpdateObserver { NotificationCenter.default.removeObserver(observer) - self.notificationCenterDidUpdateObserver = nil + notificationCenterDidUpdateObserver = nil } - if let observer = self.notificationCenterDidFailObserver { + if let observer = notificationCenterDidFailObserver { NotificationCenter.default.removeObserver(observer) - self.notificationCenterDidFailObserver = nil + notificationCenterDidFailObserver = nil } } } -extension MXRoom { - public var isMuted: Bool { +public extension MXRoom { + var isMuted: Bool { // Check whether an override rule has been defined with the roomm id as rule id. // This kind of rule is created to mute the room - guard let rule = self.overridePushRule, + guard let rule = overridePushRule, rule.actionsContains(actionType: MXPushRuleActionTypeDontNotify), rule.conditionIsEnabled(kind: .eventMatch, for: roomId) else { return false @@ -263,7 +264,7 @@ extension MXRoom { return rule.enabled } - public var isMentionsOnly: Bool { + var isMentionsOnly: Bool { // Check push rules at room level guard let rule = roomPushRule else { return false } return rule.enabled && rule.actionsContains(actionType: MXPushRuleActionTypeDontNotify) @@ -271,8 +272,7 @@ extension MXRoom { } // We could move these to their own file and make available in global namespace or move to sdk but they are only used here at the moment -fileprivate extension MXRoom { - +private extension MXRoom { typealias Completion = () -> Void func getRoomRule(from rules: [Any]) -> MXPushRule? { guard let pushRules = rules as? [MXPushRule] else { @@ -285,19 +285,18 @@ fileprivate extension MXRoom { var overridePushRule: MXPushRule? { guard let overrideRules = mxSession.notificationCenter.rules.global.override else { return nil - } + } return getRoomRule(from: overrideRules) } var roomPushRule: MXPushRule? { guard let roomRules = mxSession.notificationCenter.rules.global.room else { return nil - } + } return getRoomRule(from: roomRules) } var notificationState: RoomNotificationState { - if isMuted { return .mute } @@ -306,10 +305,9 @@ fileprivate extension MXRoom { } return .all } - } -fileprivate extension MXPushRule { +private extension MXPushRule { func actionsContains(actionType: MXPushRuleActionType) -> Bool { guard let actions = actions as? [MXPushRuleAction] else { return false @@ -323,8 +321,8 @@ fileprivate extension MXPushRule { } let ruleContainsCondition = conditions.contains { condition in guard case kind = MXPushRuleConditionType(identifier: condition.kind), - let key = condition.parameters["key"] as? String, - let pattern = condition.parameters["pattern"] as? String + let key = condition.parameters["key"] as? String, + let pattern = condition.parameters["pattern"] as? String else { return false } return key == "room_id" && pattern == roomId } diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/Service/Mock/MockRoomNotificationSettingsService.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/Service/Mock/MockRoomNotificationSettingsService.swift index 2330d07a5..9e04706de 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/Service/Mock/MockRoomNotificationSettingsService.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/Service/Mock/MockRoomNotificationSettingsService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation class MockRoomNotificationSettingsService: RoomNotificationSettingsServiceType { - static let example = MockRoomNotificationSettingsService(initialState: .all) var listener: RoomNotificationStateCallback? @@ -32,7 +31,7 @@ class MockRoomNotificationSettingsService: RoomNotificationSettingsServiceType { } func update(state: RoomNotificationState, completion: @escaping UpdateRoomNotificationStateCompletion) { - self.notificationState = state + notificationState = state completion() listener?(state) } diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/Service/RoomNotificationSettingsServiceType.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/Service/RoomNotificationSettingsServiceType.swift index 22961ad03..87443ceed 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/Service/RoomNotificationSettingsServiceType.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/Service/RoomNotificationSettingsServiceType.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,6 @@ typealias UpdateRoomNotificationStateCompletion = () -> Void typealias RoomNotificationStateCallback = (RoomNotificationState) -> Void protocol RoomNotificationSettingsServiceType { - func observeNotificationState(listener: @escaping RoomNotificationStateCallback) func update(state: RoomNotificationState, completion: @escaping UpdateRoomNotificationStateCompletion) var notificationState: RoomNotificationState { get } diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/View/FormItemButtonStyle.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/View/FormItemButtonStyle.swift index 385aacff0..c3b0a61ff 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/View/FormItemButtonStyle.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/View/FormItemButtonStyle.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/View/FormPickerItem.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/View/FormPickerItem.swift index 43aee6da8..05a3b161f 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/View/FormPickerItem.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/View/FormPickerItem.swift @@ -17,7 +17,6 @@ import SwiftUI struct FormPickerItem: View { - typealias TapCallback = () -> Void @Environment(\.theme) var theme: ThemeSwiftUI @@ -53,7 +52,6 @@ struct FormPickerItem: View { } struct FormPickerItem_Previews: PreviewProvider { - static let items = ["Item 1", "Item 2", "Item 3"] static var selected: String = items[0] static var previews: some View { diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/View/FormSectionFooter.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/View/FormSectionFooter.swift index 3465c40c9..4fc9e6211 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/View/FormSectionFooter.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/View/FormSectionFooter.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct FormSectionFooter: View { - @Environment(\.theme) var theme: ThemeSwiftUI var text: String diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/View/FormSectionHeader.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/View/FormSectionHeader.swift index 0ca745ef4..483be9164 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/View/FormSectionHeader.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/View/FormSectionHeader.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct FormSectionHeader: View { - @Environment(\.theme) var theme: ThemeSwiftUI var text: String diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/View/RoomNotificationSettings.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/View/RoomNotificationSettings.swift index 864482e4b..3034f50db 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/View/RoomNotificationSettings.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/View/RoomNotificationSettings.swift @@ -17,7 +17,6 @@ import SwiftUI struct RoomNotificationSettings: View { - @Environment(\.theme) var theme: ThemeSwiftUI @ObservedObject var viewModel: RoomNotificationSettingsSwiftUIViewModel @@ -42,7 +41,7 @@ struct RoomNotificationSettings: View { var body: some View { VectorForm { - if let avatarData = viewModel.viewState.avatarData as? AvatarInputProtocol { + if let avatarData = viewModel.viewState.avatarData as? AvatarInputProtocol { RoomNotificationSettingsHeader( avatarData: avatarData, displayName: viewModel.viewState.displayName @@ -74,7 +73,6 @@ struct RoomNotificationSettings: View { } struct RoomNotificationSettings_Previews: PreviewProvider { - static let mockViewModel = RoomNotificationSettingsSwiftUIViewModel( roomNotificationService: MockRoomNotificationSettingsService.example, avatarData: MockAvatarInput.example, diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/View/RoomNotificationSettingsHeader.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/View/RoomNotificationSettingsHeader.swift index a0b602fd8..20066b961 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/View/RoomNotificationSettingsHeader.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/View/RoomNotificationSettingsHeader.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct RoomNotificationSettingsHeader: View { - @Environment(\.theme) var theme: ThemeSwiftUI var avatarData: AvatarInputProtocol var displayName: String? diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/View/VectorForm.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/View/VectorForm.swift index 1ca9933ec..f7e4b3dbc 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/View/VectorForm.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/View/VectorForm.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct VectorForm: View { - @Environment(\.theme) var theme: ThemeSwiftUI var content: () -> Content @@ -38,12 +37,10 @@ struct VectorForm: View { ) .background(theme.colors.system) .edgesIgnoringSafeArea(.bottom) - } } struct VectorForm_Previews: PreviewProvider { - static var previews: some View { Group { VectorForm { diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/ViewModel/RoomNotificationSettingsSwiftUIViewModel.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/ViewModel/RoomNotificationSettingsSwiftUIViewModel.swift index d9b6d9bc8..1697601c2 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/ViewModel/RoomNotificationSettingsSwiftUIViewModel.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/ViewModel/RoomNotificationSettingsSwiftUIViewModel.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,17 +14,16 @@ // limitations under the License. // -import Foundation import Combine +import Foundation class RoomNotificationSettingsSwiftUIViewModel: RoomNotificationSettingsViewModel, ObservableObject { - @Published var viewState: RoomNotificationSettingsViewState lazy var cancellables = Set() override init(roomNotificationService: RoomNotificationSettingsServiceType, initialState: RoomNotificationSettingsViewState) { - self.viewState = initialState + viewState = initialState super.init(roomNotificationService: roomNotificationService, initialState: initialState) } diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/ViewModel/RoomNotificationSettingsViewModel.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/ViewModel/RoomNotificationSettingsViewModel.swift index 6b09b81d6..977945f8c 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/ViewModel/RoomNotificationSettingsViewModel.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/ViewModel/RoomNotificationSettingsViewModel.swift @@ -16,11 +16,10 @@ limitations under the License. */ -import Foundation import Combine +import Foundation class RoomNotificationSettingsViewModel: RoomNotificationSettingsViewModelType { - // MARK: - Properties // MARK: Private @@ -40,12 +39,10 @@ class RoomNotificationSettingsViewModel: RoomNotificationSettingsViewModelType { // MARK: - Setup - init( - roomNotificationService: RoomNotificationSettingsServiceType, - initialState: RoomNotificationSettingsViewState - ) { + init(roomNotificationService: RoomNotificationSettingsServiceType, + initialState: RoomNotificationSettingsViewState) { self.roomNotificationService = roomNotificationService - self.state = initialState + state = initialState self.roomNotificationService.observeNotificationState { [weak self] state in guard let self = self else { return } @@ -53,12 +50,10 @@ class RoomNotificationSettingsViewModel: RoomNotificationSettingsViewModelType { } } - convenience init( - roomNotificationService: RoomNotificationSettingsServiceType, - avatarData: AvatarProtocol?, - displayName: String?, - roomEncrypted: Bool - ) { + convenience init(roomNotificationService: RoomNotificationSettingsServiceType, + avatarData: AvatarProtocol?, + displayName: String?, + roomEncrypted: Bool) { let notificationState = Self.mapNotificationStateOnRead(encrypted: roomEncrypted, state: roomNotificationService.notificationState) let initialState = RoomNotificationSettingsViewState( @@ -71,16 +66,16 @@ class RoomNotificationSettingsViewModel: RoomNotificationSettingsViewModelType { self.init(roomNotificationService: roomNotificationService, initialState: initialState) } - // MARK: - Public + // MARK: - Public func process(viewAction: RoomNotificationSettingsViewAction) { switch viewAction { case .load: - update(viewState: self.state) + update(viewState: state) case .selectNotificationState(let state): self.state.notificationState = state case .save: - self.state.saving = true + state.saving = true roomNotificationService.update(state: state.notificationState) { [weak self] in guard let self = self else { return } self.state.saving = false @@ -103,6 +98,6 @@ class RoomNotificationSettingsViewModel: RoomNotificationSettingsViewModelType { } func update(viewState: RoomNotificationSettingsViewState) { - self.viewDelegate?.roomNotificationSettingsViewModel(self, didUpdateViewState: viewState) + viewDelegate?.roomNotificationSettingsViewModel(self, didUpdateViewState: viewState) } } diff --git a/RiotSwiftUI/Modules/Room/NotificationSettings/ViewModel/RoomNotificationSettingsViewModelType.swift b/RiotSwiftUI/Modules/Room/NotificationSettings/ViewModel/RoomNotificationSettingsViewModelType.swift index e2fe135d4..5df765f25 100644 --- a/RiotSwiftUI/Modules/Room/NotificationSettings/ViewModel/RoomNotificationSettingsViewModelType.swift +++ b/RiotSwiftUI/Modules/Room/NotificationSettings/ViewModel/RoomNotificationSettingsViewModelType.swift @@ -28,8 +28,7 @@ protocol RoomNotificationSettingsViewModelCoordinatorDelegate: AnyObject { } /// Protocol describing the view model used by `RoomNotificationSettingsViewController` -protocol RoomNotificationSettingsViewModelType { - +protocol RoomNotificationSettingsViewModelType { var viewDelegate: RoomNotificationSettingsViewModelViewDelegate? { get set } var coordinatorDelegate: RoomNotificationSettingsViewModelCoordinatorDelegate? { get set } diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/Coordinator/PollEditFormCoordinator.swift b/RiotSwiftUI/Modules/Room/PollEditForm/Coordinator/PollEditFormCoordinator.swift index 14ed174de..dcda5b5ab 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/Coordinator/PollEditFormCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/Coordinator/PollEditFormCoordinator.swift @@ -15,8 +15,8 @@ // import Foundation -import UIKit import SwiftUI +import UIKit struct PollEditFormCoordinatorParameters { let room: MXRoom @@ -24,7 +24,6 @@ struct PollEditFormCoordinatorParameters { } final class PollEditFormCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -50,7 +49,7 @@ final class PollEditFormCoordinator: Coordinator, Presentable { viewModel = PollEditFormViewModel(parameters: PollEditFormViewModelParameters(mode: .editing, pollDetails: EditFormPollDetails(type: Self.pollKindKeyToDetailsType(pollContent.kind), question: pollContent.question, - answerOptions: pollContent.answerOptions.map { $0.text }))) + answerOptions: pollContent.answerOptions.map(\.text)))) } else { viewModel = PollEditFormViewModel(parameters: PollEditFormViewModelParameters(mode: .creation, pollDetails: .default)) @@ -63,6 +62,7 @@ final class PollEditFormCoordinator: Coordinator, Presentable { } // MARK: - Public + func start() { pollEditFormViewModel.completion = { [weak self] result in guard let self = self else { return } @@ -75,7 +75,7 @@ final class PollEditFormCoordinator: Coordinator, Presentable { self.pollEditFormViewModel.startLoading() - self.parameters.room.sendPollStart(withContent: pollStartContent, threadId: nil, localEcho: nil) { [weak self] result in + self.parameters.room.sendPollStart(withContent: pollStartContent, threadId: nil, localEcho: nil) { [weak self] _ in guard let self = self else { return } self.pollEditFormViewModel.stopLoading() @@ -103,7 +103,7 @@ final class PollEditFormCoordinator: Coordinator, Presentable { self.parameters.room.sendPollUpdate(for: pollStartEvent, oldContent: oldPollContent, - newContent: newPollContent, localEcho: nil) { [weak self] result in + newContent: newPollContent, localEcho: nil) { [weak self] _ in guard let self = self else { return } self.pollEditFormViewModel.stopLoading() @@ -113,7 +113,7 @@ final class PollEditFormCoordinator: Coordinator, Presentable { MXLog.error("Failed updating poll", context: error) self.pollEditFormViewModel.stopLoading(errorAlertType: .failedUpdatingPoll) - } + } } } } @@ -121,7 +121,7 @@ final class PollEditFormCoordinator: Coordinator, Presentable { // MARK: - Presentable func toPresentable() -> UIViewController { - return pollEditFormHostingController + pollEditFormHostingController } // MARK: - Private @@ -136,21 +136,20 @@ final class PollEditFormCoordinator: Coordinator, Presentable { kind: Self.pollDetailsTypeToKindKey(details.type), maxSelections: NSNumber(value: details.maxSelections), answerOptions: options) - } private static func pollDetailsTypeToKindKey(_ type: EditFormPollType) -> String { - let mapping = [EditFormPollType.disclosed : kMXMessageContentKeyExtensiblePollKindDisclosedMSC3381, - EditFormPollType.undisclosed : kMXMessageContentKeyExtensiblePollKindUndisclosedMSC3381] + let mapping = [EditFormPollType.disclosed: kMXMessageContentKeyExtensiblePollKindDisclosedMSC3381, + EditFormPollType.undisclosed: kMXMessageContentKeyExtensiblePollKindUndisclosedMSC3381] return mapping[type] ?? kMXMessageContentKeyExtensiblePollKindDisclosedMSC3381 } private static func pollKindKeyToDetailsType(_ key: String) -> EditFormPollType { - let mapping = [kMXMessageContentKeyExtensiblePollKindDisclosed : EditFormPollType.disclosed, - kMXMessageContentKeyExtensiblePollKindDisclosedMSC3381 : EditFormPollType.disclosed, - kMXMessageContentKeyExtensiblePollKindUndisclosed : EditFormPollType.undisclosed, - kMXMessageContentKeyExtensiblePollKindUndisclosedMSC3381 : EditFormPollType.undisclosed] + let mapping = [kMXMessageContentKeyExtensiblePollKindDisclosed: EditFormPollType.disclosed, + kMXMessageContentKeyExtensiblePollKindDisclosedMSC3381: EditFormPollType.disclosed, + kMXMessageContentKeyExtensiblePollKindUndisclosed: EditFormPollType.undisclosed, + kMXMessageContentKeyExtensiblePollKindUndisclosedMSC3381: EditFormPollType.undisclosed] return mapping[key] ?? EditFormPollType.disclosed } diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormModels.swift b/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormModels.swift index 75fbf84ab..9e2171bf7 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormModels.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -82,14 +82,14 @@ struct PollEditFormViewState: BindableState { var confirmationButtonEnabled: Bool { !bindings.question.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && - bindings.answerOptions.filter({ !$0.text.isEmpty }).count >= minAnswerOptionsCount + bindings.answerOptions.filter { !$0.text.isEmpty }.count >= minAnswerOptionsCount } var addAnswerOptionButtonEnabled: Bool { bindings.answerOptions.count < maxAnswerOptionsCount } - var showLoadingIndicator: Bool = false + var showLoadingIndicator = false } struct PollEditFormViewStateBindings { diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormScreenState.swift b/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormScreenState.swift index 3d15e3be4..d4982b357 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormScreenState.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ enum MockPollEditFormScreenState: MockScreenState, CaseIterable { PollEditForm.self } - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel = PollEditFormViewModel(parameters: PollEditFormViewModelParameters(mode: .creation, pollDetails: .default)) return ([viewModel], AnyView(PollEditForm(viewModel: viewModel.context))) } diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift b/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift index 6b22edc50..0c9de40a7 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift @@ -14,20 +14,19 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI struct PollEditFormViewModelParameters { let mode: PollEditFormMode let pollDetails: EditFormPollDetails } -typealias PollEditFormViewModelType = StateStoreViewModel +typealias PollEditFormViewModelType = StateStoreViewModel class PollEditFormViewModel: PollEditFormViewModelType, PollEditFormViewModelProtocol { - - private struct Constants { + private enum Constants { static let minAnswerOptionsCount = 2 static let maxAnswerOptionsCount = 20 static let maxQuestionLength = 340 @@ -102,11 +101,11 @@ class PollEditFormViewModel: PollEditFormViewModelType, PollEditFormViewModelPro // MARK: - Private private func buildPollDetails() -> EditFormPollDetails { - return EditFormPollDetails(type: state.bindings.type, - question: state.bindings.question.text.trimmingCharacters(in: .whitespacesAndNewlines), - answerOptions: state.bindings.answerOptions.compactMap({ answerOption in - let text = answerOption.text.trimmingCharacters(in: .whitespacesAndNewlines) - return text.isEmpty ? nil : text - })) + EditFormPollDetails(type: state.bindings.type, + question: state.bindings.question.text.trimmingCharacters(in: .whitespacesAndNewlines), + answerOptions: state.bindings.answerOptions.compactMap { answerOption in + let text = answerOption.text.trimmingCharacters(in: .whitespacesAndNewlines) + return text.isEmpty ? nil : text + }) } } diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModelProtocol.swift b/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModelProtocol.swift index da2824270..ce87af060 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/Test/UI/PollEditFormUITests.swift b/RiotSwiftUI/Modules/Room/PollEditForm/Test/UI/PollEditFormUITests.swift index d56c88391..0742cf7bb 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/Test/UI/PollEditFormUITests.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/Test/UI/PollEditFormUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class PollEditFormUITests: MockScreenTestCase { func testInitialStateComponents() { diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/Test/Unit/PollEditFormViewModelTests.swift b/RiotSwiftUI/Modules/Room/PollEditForm/Test/Unit/PollEditFormViewModelTests.swift index 4f587a519..571cfd736 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/Test/Unit/PollEditFormViewModelTests.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/Test/Unit/PollEditFormViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditForm.swift b/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditForm.swift index 6f8419f28..12d8807d5 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditForm.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditForm.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct PollEditForm: View { - // MARK: - Properties // MARK: Private @@ -33,7 +32,6 @@ struct PollEditForm: View { GeometryReader { proxy in ScrollView { VStack(alignment: .leading, spacing: 32.0) { - PollEditFormTypePicker(selectedType: $viewModel.type) VStack(alignment: .leading, spacing: 16.0) { diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditFormAnswerOptionView.swift b/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditFormAnswerOptionView.swift index ccae185aa..325ae540b 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditFormAnswerOptionView.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditFormAnswerOptionView.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct PollEditFormAnswerOptionView: View { - @Environment(\.theme) private var theme: ThemeSwiftUI @State private var focused = false @@ -39,7 +38,7 @@ struct PollEditFormAnswerOptionView: View { }) .textFieldStyle(BorderedInputFieldStyle(isEditing: focused)) Button(action: onDelete) { - Image(uiImage:Asset.Images.pollDeleteOptionIcon.image) + Image(uiImage: Asset.Images.pollDeleteOptionIcon.image) } .accessibilityIdentifier("Delete answer option") } @@ -50,12 +49,8 @@ struct PollEditFormAnswerOptionView: View { struct PollEditFormAnswerOptionView_Previews: PreviewProvider { static var previews: some View { VStack(spacing: 32.0) { - PollEditFormAnswerOptionView(text: Binding.constant(""), index: 0) { - - } - PollEditFormAnswerOptionView(text: Binding.constant("Test"), index: 5) { - - } + PollEditFormAnswerOptionView(text: Binding.constant(""), index: 0) { } + PollEditFormAnswerOptionView(text: Binding.constant("Test"), index: 5) { } } } } diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditFormTypePicker.swift b/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditFormTypePicker.swift index aa8f54289..9a2b48145 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditFormTypePicker.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditFormTypePicker.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,7 +43,6 @@ private struct PollEditFormTypeButton: View { selectedType = type } label: { HStack(alignment: .top, spacing: 8.0) { - Image(uiImage: selectionImage) VStack(alignment: .leading, spacing: 2) { diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinator.swift b/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinator.swift index 5ca4563dc..0d109ecdc 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinator.swift @@ -26,7 +26,6 @@ enum RoomAccessCoordinatorCoordinatorAction { @objcMembers final class RoomAccessCoordinator: Coordinator { - // MARK: - Properties // MARK: Private @@ -35,7 +34,7 @@ final class RoomAccessCoordinator: Coordinator { private var upgradedRoomId: String? private var navigationRouter: NavigationRouterType { - return self.parameters.navigationRouter + parameters.navigationRouter } // MARK: Public @@ -58,32 +57,31 @@ final class RoomAccessCoordinator: Coordinator { init(parameters: RoomAccessCoordinatorParameters) { self.parameters = parameters - } + } // MARK: - Public - func start() { MXLog.debug("[RoomAccessCoordinator] did start.") - let rootCoordinator = self.createRoomAccessTypeCoordinator() + let rootCoordinator = createRoomAccessTypeCoordinator() rootCoordinator.start() - self.add(childCoordinator: rootCoordinator) - self.accessCoordinator = rootCoordinator + add(childCoordinator: rootCoordinator) + accessCoordinator = rootCoordinator - if self.navigationRouter.modules.isEmpty == false { - self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in + if navigationRouter.modules.isEmpty == false { + navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in self?.remove(childCoordinator: rootCoordinator) }) } else { - self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in + navigationRouter.setRootModule(rootCoordinator) { [weak self] in self?.remove(childCoordinator: rootCoordinator) } } } func toPresentable() -> UIViewController { - return self.navigationRouter.toPresentable() + navigationRouter.toPresentable() } // MARK: - Private @@ -91,7 +89,7 @@ final class RoomAccessCoordinator: Coordinator { func pushScreen(with coordinator: Coordinator & Presentable) { add(childCoordinator: coordinator) - self.navigationRouter.push(coordinator, animated: true, popCompletion: { [weak self] in + navigationRouter.push(coordinator, animated: true, popCompletion: { [weak self] in self?.remove(childCoordinator: coordinator) }) @@ -103,13 +101,13 @@ final class RoomAccessCoordinator: Coordinator { coordinator.toPresentable().modalPresentationStyle = .overFullScreen coordinator.toPresentable().modalTransitionStyle = .crossDissolve - self.navigationRouter.present(coordinator, animated: true) + navigationRouter.present(coordinator, animated: true) coordinator.start() } private func createRoomAccessTypeCoordinator() -> RoomAccessTypeChooserCoordinator { - let coordinator: RoomAccessTypeChooserCoordinator = RoomAccessTypeChooserCoordinator(parameters: RoomAccessTypeChooserCoordinatorParameters(roomId: parameters.room.roomId, allowsRoomUpgrade: parameters.allowsRoomUpgrade, session: parameters.room.mxSession)) + let coordinator = RoomAccessTypeChooserCoordinator(parameters: RoomAccessTypeChooserCoordinatorParameters(roomId: parameters.room.roomId, allowsRoomUpgrade: parameters.allowsRoomUpgrade, session: parameters.room.mxSession)) coordinator.callback = { [weak self] result in guard let self = self else { return } @@ -132,7 +130,8 @@ final class RoomAccessCoordinator: Coordinator { let paramaters = MatrixItemChooserCoordinatorParameters( session: parameters.room.mxSession, viewProvider: RoomRestrictedAccessSpaceChooserViewProvider(navTitle: VectorL10n.roomAccessSettingsScreenNavTitle), - itemsProcessor: RoomRestrictedAccessSpaceChooserItemsProcessor(roomId: roomId, session: parameters.room.mxSession)) + itemsProcessor: RoomRestrictedAccessSpaceChooserItemsProcessor(roomId: roomId, session: parameters.room.mxSession) + ) let coordinator = MatrixItemChooserCoordinator(parameters: paramaters) coordinator.completion = { [weak self] result in guard let self = self else { return } @@ -154,7 +153,8 @@ final class RoomAccessCoordinator: Coordinator { session: parameters.room.mxSession, roomId: roomId, parentSpaceId: parameters.parentSpaceId, - versionOverride: versionOverride) + versionOverride: versionOverride + ) let coordinator = RoomUpgradeCoordinator(parameters: paramaters) coordinator.completion = { [weak self] result in diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinatorBridgePresenter.swift b/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinatorBridgePresenter.swift index 78a3215ba..623100b3b 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinatorBridgePresenter.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinatorBridgePresenter.swift @@ -1,4 +1,5 @@ // +import MatrixSDK // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,7 +15,6 @@ // limitations under the License. // import UIKit -import MatrixSDK @objc protocol RoomAccessCoordinatorBridgePresenterDelegate { func roomAccessCoordinatorBridgePresenterDelegate(_ coordinatorBridgePresenter: RoomAccessCoordinatorBridgePresenter, didCancelRoomWithId roomId: String) @@ -27,7 +27,6 @@ import MatrixSDK /// Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator. @objcMembers final class RoomAccessCoordinatorBridgePresenter: NSObject { - // MARK: - Properties // MARK: Private @@ -82,7 +81,7 @@ final class RoomAccessCoordinatorBridgePresenter: NSObject { } func dismiss(animated: Bool, completion: (() -> Void)?) { - guard let coordinator = self.coordinator else { + guard let coordinator = coordinator else { return } coordinator.toPresentable().dismiss(animated: animated) { @@ -98,13 +97,11 @@ final class RoomAccessCoordinatorBridgePresenter: NSObject { // MARK: - UIAdaptivePresentationControllerDelegate extension RoomAccessCoordinatorBridgePresenter: UIAdaptivePresentationControllerDelegate { - func roomNotificationSettingsCoordinatorDidComplete(_ presentationController: UIPresentationController) { - if let roomId = self.coordinator?.currentRoomId { - self.delegate?.roomAccessCoordinatorBridgePresenterDelegate(self, didCancelRoomWithId: roomId) + if let roomId = coordinator?.currentRoomId { + delegate?.roomAccessCoordinatorBridgePresenterDelegate(self, didCancelRoomWithId: roomId) } else { - self.delegate?.roomAccessCoordinatorBridgePresenterDelegate(self, didCancelRoomWithId: self.room.roomId) + delegate?.roomAccessCoordinatorBridgePresenterDelegate(self, didCancelRoomWithId: room.roomId) } } - } diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinatorParameters.swift b/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinatorParameters.swift index a4459ea0a..50e128407 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinatorParameters.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinatorParameters.swift @@ -20,7 +20,6 @@ import Foundation /// RoomAccessCoordinator input parameters struct RoomAccessCoordinatorParameters { - /// The Matrix room let room: MXRoom diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Coordinator/RoomAccessTypeChooserCoordinator.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Coordinator/RoomAccessTypeChooserCoordinator.swift index a46542a42..a6e6d2280 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Coordinator/RoomAccessTypeChooserCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Coordinator/RoomAccessTypeChooserCoordinator.swift @@ -23,7 +23,6 @@ struct RoomAccessTypeChooserCoordinatorParameters { } final class RoomAccessTypeChooserCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -70,10 +69,10 @@ final class RoomAccessTypeChooserCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.roomAccessTypeChooserHostingController + roomAccessTypeChooserHostingController } func handleRoomUpgradeResult(_ result: RoomUpgradeCoordinatorResult) { - self.roomAccessTypeChooserViewModel.handleRoomUpgradeResult(result) + roomAccessTypeChooserViewModel.handleRoomUpgradeResult(result) } } diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/MockRoomAccessTypeChooserScreenState.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/MockRoomAccessTypeChooserScreenState.swift index 47037d07f..fd8e74103 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/MockRoomAccessTypeChooserScreenState.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/MockRoomAccessTypeChooserScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation import SwiftUI - /// Using an enum for the screen allows you define the different state cases with /// the relevant associated data for each case. enum MockRoomAccessTypeChooserScreenState: MockScreenState, CaseIterable { @@ -42,7 +41,7 @@ enum MockRoomAccessTypeChooserScreenState: MockScreenState, CaseIterable { service = MockRoomAccessTypeChooserService(accessItems: [ RoomAccessTypeChooserAccessItem(id: .private, isSelected: true, title: VectorL10n.private, detail: VectorL10n.roomAccessSettingsScreenPrivateMessage, badgeText: nil), RoomAccessTypeChooserAccessItem(id: .restricted, isSelected: false, title: VectorL10n.createRoomTypeRestricted, detail: VectorL10n.roomAccessSettingsScreenRestrictedMessage, badgeText: VectorL10n.roomAccessSettingsScreenUpgradeRequired), - RoomAccessTypeChooserAccessItem(id: .public, isSelected: false, title: VectorL10n.public, detail: VectorL10n.roomAccessSettingsScreenPublicMessage, badgeText: nil), + RoomAccessTypeChooserAccessItem(id: .public, isSelected: false, title: VectorL10n.public, detail: VectorL10n.roomAccessSettingsScreenPublicMessage, badgeText: nil) ]) } let viewModel = RoomAccessTypeChooserViewModel(roomAccessTypeChooserService: service) diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserModels.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserModels.swift index 2797adab9..4ebad9737 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserModels.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift index 7b97bd753..c05ff9ac5 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,14 +14,13 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias RoomAccessTypeChooserViewModelType = StateStoreViewModel + RoomAccessTypeChooserStateAction, + RoomAccessTypeChooserViewAction> class RoomAccessTypeChooserViewModel: RoomAccessTypeChooserViewModelType, RoomAccessTypeChooserViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -43,7 +42,8 @@ class RoomAccessTypeChooserViewModel: RoomAccessTypeChooserViewModelType, RoomAc private static func defaultState(roomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol) -> RoomAccessTypeChooserViewState { let bindings = RoomAccessTypeChooserViewModelBindings( showUpgradeRoomAlert: roomAccessTypeChooserService.roomUpgradeRequiredSubject.value, - waitingMessage: roomAccessTypeChooserService.waitingMessageSubject.value, isLoading: roomAccessTypeChooserService.waitingMessageSubject.value != nil) + waitingMessage: roomAccessTypeChooserService.waitingMessageSubject.value, isLoading: roomAccessTypeChooserService.waitingMessageSubject.value != nil + ) return RoomAccessTypeChooserViewState(accessItems: roomAccessTypeChooserService.accessItemsSubject.value, bindings: bindings) } @@ -130,7 +130,7 @@ class RoomAccessTypeChooserViewModel: RoomAccessTypeChooserViewModelType, RoomAc private func didSelect(accessType: RoomAccessTypeChooserAccessType) { roomAccessTypeChooserService.updateSelection(with: accessType) - if accessType == .restricted && !roomAccessTypeChooserService.roomUpgradeRequiredSubject.value { + if accessType == .restricted, !roomAccessTypeChooserService.roomUpgradeRequiredSubject.value { callback?(.spaceSelection(roomAccessTypeChooserService.currentRoomId, .restricted)) } } diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModelProtocol.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModelProtocol.swift index 92b0b430a..8fdbc6f11 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/MatrixSDK/RoomAccessTypeChooserService.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/MatrixSDK/RoomAccessTypeChooserService.swift index 1058f6b3b..ec307ca7f 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/MatrixSDK/RoomAccessTypeChooserService.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/MatrixSDK/RoomAccessTypeChooserService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,12 +14,11 @@ // limitations under the License. // -import Foundation import Combine +import Foundation import MatrixSDK class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { - // MARK: - Properties // MARK: Private @@ -40,6 +39,7 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { accessItemsSubject.send(accessItems) } } + private(set) var selectedType: RoomAccessTypeChooserAccessType = .private { didSet { for (index, item) in accessItems.enumerated() { @@ -48,6 +48,7 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { accessItemsSubject.send(accessItems) } } + private var roomJoinRule: MXRoomJoinRule = .private private var currentOperation: MXHTTPOperation? @@ -67,8 +68,8 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { self.roomId = roomId self.allowsRoomUpgrade = allowsRoomUpgrade self.session = session - self.currentRoomId = roomId - self.versionOverride = session.homeserverCapabilitiesService.versionOverrideForFeature(.restricted) + currentRoomId = roomId + versionOverride = session.homeserverCapabilitiesService.versionOverrideForFeature(.restricted) roomUpgradeRequiredSubject = CurrentValueSubject(false) waitingMessageSubject = CurrentValueSubject(nil) @@ -92,7 +93,7 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { self.selectedType = selectedType if selectedType == .restricted { - if roomUpgradeRequired && roomUpgradeRequiredSubject.value == false { + if roomUpgradeRequired, roomUpgradeRequiredSubject.value == false { roomUpgradeRequiredSubject.send(true) } } @@ -108,14 +109,14 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { let _joinRule: MXRoomJoinRule? - switch self.selectedType { + switch selectedType { case .private: _joinRule = .invite case .public: _joinRule = .public case .restricted: _joinRule = nil - if roomUpgradeRequired && roomUpgradeRequiredSubject.value == false { + if roomUpgradeRequired, roomUpgradeRequiredSubject.value == false { roomUpgradeRequiredSubject.send(true) } else { completion() @@ -123,7 +124,7 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { } if let joinRule = _joinRule { - self.waitingMessageSubject.send(VectorL10n.roomAccessSettingsScreenSettingRoomAccess) + waitingMessageSubject.send(VectorL10n.roomAccessSettingsScreenSettingRoomAccess) room.setJoinRule(joinRule) { [weak self] response in guard let self = self else { return } @@ -140,7 +141,7 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { } func updateRoomId(with roomId: String) { - self.currentRoomId = roomId + currentRoomId = roomId readRoomState() } @@ -148,17 +149,17 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { private func setupAccessItems() { guard let spaceService = session.spaceService, let ancestors = spaceService.ancestorsPerRoomId[currentRoomId], !ancestors.isEmpty, allowsRoomUpgrade || !roomUpgradeRequired else { - self.accessItems = [ + accessItems = [ RoomAccessTypeChooserAccessItem(id: .private, isSelected: false, title: VectorL10n.private, detail: VectorL10n.roomAccessSettingsScreenPrivateMessage, badgeText: nil), - RoomAccessTypeChooserAccessItem(id: .public, isSelected: false, title: VectorL10n.public, detail: VectorL10n.roomAccessSettingsScreenPublicMessage, badgeText: nil), + RoomAccessTypeChooserAccessItem(id: .public, isSelected: false, title: VectorL10n.public, detail: VectorL10n.roomAccessSettingsScreenPublicMessage, badgeText: nil) ] return } - self.accessItems = [ + accessItems = [ RoomAccessTypeChooserAccessItem(id: .private, isSelected: false, title: VectorL10n.private, detail: VectorL10n.roomAccessSettingsScreenPrivateMessage, badgeText: nil), RoomAccessTypeChooserAccessItem(id: .restricted, isSelected: false, title: VectorL10n.createRoomTypeRestricted, detail: VectorL10n.roomAccessSettingsScreenRestrictedMessage, badgeText: roomUpgradeRequired ? VectorL10n.roomAccessSettingsScreenUpgradeRequired : VectorL10n.roomAccessSettingsScreenEditSpaces), - RoomAccessTypeChooserAccessItem(id: .public, isSelected: false, title: VectorL10n.public, detail: VectorL10n.roomAccessSettingsScreenPublicMessage, badgeText: nil), + RoomAccessTypeChooserAccessItem(id: .public, isSelected: false, title: VectorL10n.public, detail: VectorL10n.roomAccessSettingsScreenPublicMessage, badgeText: nil) ] accessItemsSubject.send(accessItems) diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/Mock/MockRoomAccessTypeChooserService.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/Mock/MockRoomAccessTypeChooserService.swift index 5bd115685..f0e4a3b11 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/Mock/MockRoomAccessTypeChooserService.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/Mock/MockRoomAccessTypeChooserService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,15 +14,14 @@ // limitations under the License. // -import Foundation import Combine +import Foundation class MockRoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { - static let mockAccessItems: [RoomAccessTypeChooserAccessItem] = [ RoomAccessTypeChooserAccessItem(id: .private, isSelected: true, title: VectorL10n.private, detail: VectorL10n.roomAccessSettingsScreenPrivateMessage, badgeText: nil), RoomAccessTypeChooserAccessItem(id: .restricted, isSelected: false, title: VectorL10n.createRoomTypeRestricted, detail: VectorL10n.roomAccessSettingsScreenRestrictedMessage, badgeText: VectorL10n.roomAccessSettingsScreenUpgradeRequired), - RoomAccessTypeChooserAccessItem(id: .public, isSelected: false, title: VectorL10n.public, detail: VectorL10n.roomAccessSettingsScreenPublicMessage, badgeText: nil), + RoomAccessTypeChooserAccessItem(id: .public, isSelected: false, title: VectorL10n.public, detail: VectorL10n.roomAccessSettingsScreenPublicMessage, badgeText: nil) ] private(set) var accessItemsSubject: CurrentValueSubject<[RoomAccessTypeChooserAccessItem], Never> @@ -31,9 +30,9 @@ class MockRoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { private(set) var errorSubject: CurrentValueSubject private(set) var selectedType: RoomAccessTypeChooserAccessType = .private - var currentRoomId: String = "!aaabaa:matrix.org" + var currentRoomId = "!aaabaa:matrix.org" var versionOverride: String? { - return "9" + "9" } init(accessItems: [RoomAccessTypeChooserAccessItem] = mockAccessItems) { @@ -44,18 +43,14 @@ class MockRoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { } func simulateUpdate(accessItems: [RoomAccessTypeChooserAccessItem]) { - self.accessItemsSubject.send(accessItems) + accessItemsSubject.send(accessItems) } - func updateSelection(with selectedType: RoomAccessTypeChooserAccessType) { - - } + func updateSelection(with selectedType: RoomAccessTypeChooserAccessType) { } func updateRoomId(with roomId: String) { currentRoomId = roomId } - func applySelection(completion: @escaping () -> Void) { - - } + func applySelection(completion: @escaping () -> Void) { } } diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/RoomAccessTypeChooserServiceProtocol.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/RoomAccessTypeChooserServiceProtocol.swift index b96bb2ad0..3f4935e6d 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/RoomAccessTypeChooserServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/RoomAccessTypeChooserServiceProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation protocol RoomAccessTypeChooserServiceProtocol { var accessItemsSubject: CurrentValueSubject<[RoomAccessTypeChooserAccessItem], Never> { get } diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Test/UI/RoomAccessTypeChooserUITests.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Test/UI/RoomAccessTypeChooserUITests.swift index d6f5b3dbb..d7203387b 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Test/UI/RoomAccessTypeChooserUITests.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Test/UI/RoomAccessTypeChooserUITests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class RoomAccessTypeChooserUITests: MockScreenTestCase { // Tests to be implemented. diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Test/Unit/RoomAccessTypeChooserViewModelTests.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Test/Unit/RoomAccessTypeChooserViewModelTests.swift index 7eda5de05..f7522b442 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Test/Unit/RoomAccessTypeChooserViewModelTests.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Test/Unit/RoomAccessTypeChooserViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,11 +14,9 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI -class RoomAccessTypeChooserViewModelTests: XCTestCase { - -} +class RoomAccessTypeChooserViewModelTests: XCTestCase { } diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooser.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooser.swift index 9786273d3..f7d076d6d 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooser.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooser.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct RoomAccessTypeChooser: View { - // MARK: - Properties // MARK: Private @@ -55,7 +54,7 @@ struct RoomAccessTypeChooser: View { @ViewBuilder private var listContent: some View { - ScrollView{ + ScrollView { VStack(alignment: .leading) { Text(VectorL10n.roomAccessSettingsScreenTitle) .foregroundColor(theme.colors.primaryContent) @@ -84,7 +83,6 @@ struct RoomAccessTypeChooser: View { // MARK: - Previews struct RoomAccessTypeChooser_Previews: PreviewProvider { - static let stateRenderer = MockRoomAccessTypeChooserScreenState.stateRenderer static var previews: some View { stateRenderer.screenGroup(addNavigation: true) diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooserRow.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooserRow.swift index a38cd0efe..b5b3776c1 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooserRow.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooserRow.swift @@ -17,7 +17,6 @@ import SwiftUI struct RoomAccessTypeChooserRow: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/Coordinator/RoomRestrictedAccessSpaceChooserViewProvider.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/Coordinator/RoomRestrictedAccessSpaceChooserViewProvider.swift index 389716a8f..fd1b5c71c 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/Coordinator/RoomRestrictedAccessSpaceChooserViewProvider.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/Coordinator/RoomRestrictedAccessSpaceChooserViewProvider.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI class RoomRestrictedAccessSpaceChooserViewProvider: MatrixItemChooserCoordinatorViewProvider { - private let navTitle: String? init(navTitle: String?) { @@ -25,6 +24,6 @@ class RoomRestrictedAccessSpaceChooserViewProvider: MatrixItemChooserCoordinator } func view(with viewModel: MatrixItemChooserViewModelType.Context) -> AnyView { - return AnyView(RoomRestrictedAccessSpaceChooserSelector(viewModel: viewModel, navTitle: navTitle)) + AnyView(RoomRestrictedAccessSpaceChooserSelector(viewModel: viewModel, navTitle: navTitle)) } } diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/Service/MatrixSDK/RoomRestrictedAccessSpaceChooserItemsProcessor.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/Service/MatrixSDK/RoomRestrictedAccessSpaceChooserItemsProcessor.swift index 15d334091..679d41016 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/Service/MatrixSDK/RoomRestrictedAccessSpaceChooserItemsProcessor.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/Service/MatrixSDK/RoomRestrictedAccessSpaceChooserItemsProcessor.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation class RoomRestrictedAccessSpaceChooserItemsProcessor: MatrixItemChooserProcessorProtocol { - // MARK: Private private let roomId: String @@ -28,7 +27,7 @@ class RoomRestrictedAccessSpaceChooserItemsProcessor: MatrixItemChooserProcessor init(roomId: String, session: MXSession) { self.roomId = roomId self.session = session - self.dataSource = MatrixItemChooserRoomRestrictedAllowedParentsDataSource(roomId: roomId) + dataSource = MatrixItemChooserRoomRestrictedAllowedParentsDataSource(roomId: roomId) } // MARK: MatrixItemChooserSelectionProcessorProtocol @@ -50,7 +49,7 @@ class RoomRestrictedAccessSpaceChooserItemsProcessor: MatrixItemChooserProcessor } } - func isItemIncluded(_ item: (MatrixListItemData)) -> Bool { - return true + func isItemIncluded(_ item: MatrixListItemData) -> Bool { + true } } diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/View/RoomRestrictedAccessSpaceChooserSelector.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/View/RoomRestrictedAccessSpaceChooserSelector.swift index 3b52c9e41..25019eb15 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/View/RoomRestrictedAccessSpaceChooserSelector.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/View/RoomRestrictedAccessSpaceChooserSelector.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct RoomRestrictedAccessSpaceChooserSelector: View { - // MARK: Properties @ObservedObject var viewModel: MatrixItemChooserViewModel.Context @@ -51,5 +50,4 @@ struct RoomRestrictedAccessSpaceChooserSelector: View { } } } - } diff --git a/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinator.swift b/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinator.swift index ffdfc5e3f..e18ca2bdf 100644 --- a/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinator.swift @@ -24,7 +24,6 @@ enum RoomSuggestionCoordinatorCoordinatorAction { @objcMembers final class RoomSuggestionCoordinator: Coordinator { - // MARK: - Properties // MARK: Private @@ -32,7 +31,7 @@ final class RoomSuggestionCoordinator: Coordinator { private let parameters: RoomSuggestionCoordinatorParameters private var navigationRouter: NavigationRouterType { - return self.parameters.navigationRouter + parameters.navigationRouter } // MARK: Public @@ -46,31 +45,30 @@ final class RoomSuggestionCoordinator: Coordinator { init(parameters: RoomSuggestionCoordinatorParameters) { self.parameters = parameters - } + } // MARK: - Public - func start() { MXLog.debug("[RoomSuggestionCoordinator] did start.") - let rootCoordinator = self.createRoomSuggestionSpaceChooser() + let rootCoordinator = createRoomSuggestionSpaceChooser() rootCoordinator.start() - self.add(childCoordinator: rootCoordinator) + add(childCoordinator: rootCoordinator) - if self.navigationRouter.modules.isEmpty == false { - self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in + if navigationRouter.modules.isEmpty == false { + navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in self?.remove(childCoordinator: rootCoordinator) }) } else { - self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in + navigationRouter.setRootModule(rootCoordinator) { [weak self] in self?.remove(childCoordinator: rootCoordinator) } } } func toPresentable() -> UIViewController { - return self.navigationRouter.toPresentable() + navigationRouter.toPresentable() } // MARK: - Private @@ -78,7 +76,7 @@ final class RoomSuggestionCoordinator: Coordinator { func pushScreen(with coordinator: Coordinator & Presentable) { add(childCoordinator: coordinator) - self.navigationRouter.push(coordinator, animated: true, popCompletion: { [weak self] in + navigationRouter.push(coordinator, animated: true, popCompletion: { [weak self] in self?.remove(childCoordinator: coordinator) }) @@ -91,7 +89,8 @@ final class RoomSuggestionCoordinator: Coordinator { title: VectorL10n.roomSuggestionSettingsScreenTitle, detail: VectorL10n.roomSuggestionSettingsScreenMessage, viewProvider: RoomSuggestionSpaceChooserViewProvider(navTitle: VectorL10n.roomAccessSettingsScreenNavTitle), - itemsProcessor: RoomSuggestionSpaceChooserItemsProcessor(roomId: parameters.room.roomId, session: parameters.room.mxSession)) + itemsProcessor: RoomSuggestionSpaceChooserItemsProcessor(roomId: parameters.room.roomId, session: parameters.room.mxSession) + ) let coordinator = MatrixItemChooserCoordinator(parameters: paramaters) coordinator.completion = { [weak self] result in guard let self = self else { return } @@ -107,5 +106,4 @@ final class RoomSuggestionCoordinator: Coordinator { } return coordinator } - } diff --git a/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinatorBridgePresenter.swift b/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinatorBridgePresenter.swift index a6bfa2ee5..7c1e71c02 100644 --- a/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinatorBridgePresenter.swift +++ b/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinatorBridgePresenter.swift @@ -26,7 +26,6 @@ import UIKit /// Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator. @objcMembers final class RoomSuggestionCoordinatorBridgePresenter: NSObject { - // MARK: - Properties // MARK: Private @@ -70,7 +69,7 @@ final class RoomSuggestionCoordinatorBridgePresenter: NSObject { } func dismiss(animated: Bool, completion: (() -> Void)?) { - guard let coordinator = self.coordinator else { + guard let coordinator = coordinator else { return } coordinator.toPresentable().dismiss(animated: animated) { @@ -86,9 +85,7 @@ final class RoomSuggestionCoordinatorBridgePresenter: NSObject { // MARK: - UIAdaptivePresentationControllerDelegate extension RoomSuggestionCoordinatorBridgePresenter: UIAdaptivePresentationControllerDelegate { - func roomNotificationSettingsCoordinatorDidComplete(_ presentationController: UIPresentationController) { - self.delegate?.roomSuggestionCoordinatorBridgePresenterDelegateDidCancel(self) + delegate?.roomSuggestionCoordinatorBridgePresenterDelegateDidCancel(self) } - } diff --git a/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinatorParameters.swift b/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinatorParameters.swift index 1ac2ce0eb..f3bc5580f 100644 --- a/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinatorParameters.swift +++ b/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinatorParameters.swift @@ -18,7 +18,6 @@ import Foundation /// RoomSuggestionCoordinator input parameters struct RoomSuggestionCoordinatorParameters { - /// The Matrix room let room: MXRoom diff --git a/RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/Coordinator/RoomSuggestionSpaceChooserViewProvider.swift b/RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/Coordinator/RoomSuggestionSpaceChooserViewProvider.swift index a5b261fd0..7c04225ee 100644 --- a/RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/Coordinator/RoomSuggestionSpaceChooserViewProvider.swift +++ b/RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/Coordinator/RoomSuggestionSpaceChooserViewProvider.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI class RoomSuggestionSpaceChooserViewProvider: MatrixItemChooserCoordinatorViewProvider { - private let navTitle: String? init(navTitle: String?) { @@ -25,6 +24,6 @@ class RoomSuggestionSpaceChooserViewProvider: MatrixItemChooserCoordinatorViewPr } func view(with viewModel: MatrixItemChooserViewModelType.Context) -> AnyView { - return AnyView(RoomSuggestionSpaceChooserSelector(viewModel: viewModel, navTitle: navTitle)) + AnyView(RoomSuggestionSpaceChooserSelector(viewModel: viewModel, navTitle: navTitle)) } } diff --git a/RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/Service/MatrixSDK/RoomSuggestionSpaceChooserItemsProcessor.swift b/RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/Service/MatrixSDK/RoomSuggestionSpaceChooserItemsProcessor.swift index 016e17dd1..6a8d664fc 100644 --- a/RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/Service/MatrixSDK/RoomSuggestionSpaceChooserItemsProcessor.swift +++ b/RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/Service/MatrixSDK/RoomSuggestionSpaceChooserItemsProcessor.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,6 @@ public enum RoomSuggestionSpaceChooserItemsProcessorError: Int, Error { } class RoomSuggestionSpaceChooserItemsProcessor: MatrixItemChooserProcessorProtocol { - // MARK: Private private let roomId: String @@ -35,7 +34,7 @@ class RoomSuggestionSpaceChooserItemsProcessor: MatrixItemChooserProcessorProtoc init(roomId: String, session: MXSession) { self.roomId = roomId self.session = session - self.dataSource = MatrixItemChooserRoomDirectParentsDataSource(roomId: roomId, preselectionMode: .suggestedRoom) + dataSource = MatrixItemChooserRoomDirectParentsDataSource(roomId: roomId, preselectionMode: .suggestedRoom) } deinit { @@ -43,6 +42,7 @@ class RoomSuggestionSpaceChooserItemsProcessor: MatrixItemChooserProcessorProtoc NotificationCenter.default.removeObserver(observer) } } + // MARK: MatrixItemChooserSelectionProcessorProtocol private(set) var dataSource: MatrixItemChooserDataSource @@ -53,11 +53,11 @@ class RoomSuggestionSpaceChooserItemsProcessor: MatrixItemChooserProcessorProtoc let unselectedItems: [String] let selectedItems: [String] if let preselectedItems = dataSource.preselectedItemIds { - unselectedItems = preselectedItems.compactMap({ itemId in - return !itemsIds.contains(itemId) ? itemId : nil - }) + unselectedItems = preselectedItems.compactMap { itemId in + !itemsIds.contains(itemId) ? itemId : nil + } selectedItems = itemsIds.compactMap { itemId in - return !preselectedItems.contains(itemId) ? itemId : nil + !preselectedItems.contains(itemId) ? itemId : nil } } else { unselectedItems = [] @@ -78,7 +78,7 @@ class RoomSuggestionSpaceChooserItemsProcessor: MatrixItemChooserProcessorProtoc if let firstError = self.computationErrorList.first { completion(.failure(firstError)) } else { - self.didBuildSpaceGraphObserver = NotificationCenter.default.addObserver(forName: MXSpaceService.didBuildSpaceGraph, object: nil, queue: OperationQueue.main) { [weak self] notification in + self.didBuildSpaceGraphObserver = NotificationCenter.default.addObserver(forName: MXSpaceService.didBuildSpaceGraph, object: nil, queue: OperationQueue.main) { [weak self] _ in guard let self = self else { return } if let observer = self.didBuildSpaceGraphObserver { @@ -93,8 +93,8 @@ class RoomSuggestionSpaceChooserItemsProcessor: MatrixItemChooserProcessorProtoc } } - func isItemIncluded(_ item: (MatrixListItemData)) -> Bool { - return true + func isItemIncluded(_ item: MatrixListItemData) -> Bool { + true } // MARK: - Private diff --git a/RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/View/RoomSuggestionSpaceChooserSelector.swift b/RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/View/RoomSuggestionSpaceChooserSelector.swift index d0c1e4e28..4b2f5d2bc 100644 --- a/RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/View/RoomSuggestionSpaceChooserSelector.swift +++ b/RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/View/RoomSuggestionSpaceChooserSelector.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct RoomSuggestionSpaceChooserSelector: View { - // MARK: Properties @ObservedObject var viewModel: MatrixItemChooserViewModel.Context @@ -48,5 +47,4 @@ struct RoomSuggestionSpaceChooserSelector: View { } } } - } diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/Coordinator/RoomUpgradeCoordinator.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/Coordinator/RoomUpgradeCoordinator.swift index fec7cd3e0..bc89e53cb 100644 --- a/RiotSwiftUI/Modules/Room/RoomUpgrade/Coordinator/RoomUpgradeCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/Coordinator/RoomUpgradeCoordinator.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import SwiftUI import MatrixSDK +import SwiftUI struct RoomUpgradeCoordinatorParameters { let session: MXSession @@ -25,7 +25,6 @@ struct RoomUpgradeCoordinatorParameters { } final class RoomUpgradeCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -53,6 +52,7 @@ final class RoomUpgradeCoordinator: Coordinator, Presentable { } // MARK: - Public + func start() { MXLog.debug("[RoomUpgradeCoordinator] did start.") roomUpgradeViewModel.completion = { [weak self] result in @@ -68,6 +68,6 @@ final class RoomUpgradeCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.roomUpgradeHostingController + roomUpgradeHostingController } } diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/MockRoomUpgradeScreenState.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/MockRoomUpgradeScreenState.swift index 0052fd05b..bca1f0ea7 100644 --- a/RiotSwiftUI/Modules/Room/RoomUpgrade/MockRoomUpgradeScreenState.swift +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/MockRoomUpgradeScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,7 +36,7 @@ enum MockRoomUpgradeScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let service: MockRoomUpgradeService switch self { case .initial: diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeModels.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeModels.swift index 6d74a7f30..37966272e 100644 --- a/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeModels.swift +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModel.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModel.swift index 4a8b876d2..c61dd0323 100644 --- a/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModel.swift +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModel.swift @@ -14,14 +14,13 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias RoomUpgradeViewModelType = StateStoreViewModel + Never, + RoomUpgradeViewAction> class RoomUpgradeViewModel: RoomUpgradeViewModelType, RoomUpgradeViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -35,7 +34,7 @@ class RoomUpgradeViewModel: RoomUpgradeViewModelType, RoomUpgradeViewModelProtoc // MARK: - Setup static func makeRoomUpgradeViewModel(roomUpgradeService: RoomUpgradeServiceProtocol) -> RoomUpgradeViewModelProtocol { - return RoomUpgradeViewModel(roomUpgradeService: roomUpgradeService) + RoomUpgradeViewModel(roomUpgradeService: roomUpgradeService) } private init(roomUpgradeService: RoomUpgradeServiceProtocol) { @@ -45,7 +44,7 @@ class RoomUpgradeViewModel: RoomUpgradeViewModelType, RoomUpgradeViewModelProtoc } private static func defaultState(roomUpgradeService: RoomUpgradeServiceProtocol) -> RoomUpgradeViewState { - return RoomUpgradeViewState(waitingMessage: nil, isLoading: false, parentSpaceName: roomUpgradeService.parentSpaceName) + RoomUpgradeViewState(waitingMessage: nil, isLoading: false, parentSpaceName: roomUpgradeService.parentSpaceName) } private func setupObservers() { @@ -53,7 +52,7 @@ class RoomUpgradeViewModel: RoomUpgradeViewModelType, RoomUpgradeViewModelProtoc .upgradingSubject .sink { [weak self] isUpgrading in self?.state.isLoading = isUpgrading - self?.state.waitingMessage = isUpgrading ? VectorL10n.roomAccessSettingsScreenUpgradeAlertUpgrading: nil + self?.state.waitingMessage = isUpgrading ? VectorL10n.roomAccessSettingsScreenUpgradeAlertUpgrading : nil } .store(in: &cancellables) } @@ -65,7 +64,7 @@ class RoomUpgradeViewModel: RoomUpgradeViewModelType, RoomUpgradeViewModelProtoc case .cancel: completion?(.cancel(roomUpgradeService.currentRoomId)) case .done(let autoInviteUsers): - roomUpgradeService.upgradeRoom(autoInviteUsers: autoInviteUsers) { [weak self] success, roomId in + roomUpgradeService.upgradeRoom(autoInviteUsers: autoInviteUsers) { [weak self] success, _ in guard let self = self else { return } if success { self.completion?(.done(self.roomUpgradeService.currentRoomId)) diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModelProtocol.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModelProtocol.swift index eb6136d1d..e9dcefdaa 100644 --- a/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol RoomUpgradeViewModelProtocol { - var completion: ((RoomUpgradeViewModelResult) -> Void)? { get set } static func makeRoomUpgradeViewModel(roomUpgradeService: RoomUpgradeServiceProtocol) -> RoomUpgradeViewModelProtocol var context: RoomUpgradeViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/MatrixSDK/RoomUpgradeService.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/MatrixSDK/RoomUpgradeService.swift index 02d9b5b9f..adc01c5ee 100644 --- a/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/MatrixSDK/RoomUpgradeService.swift +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/MatrixSDK/RoomUpgradeService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,12 +14,11 @@ // limitations under the License. // -import Foundation import Combine +import Foundation import MatrixSDK class RoomUpgradeService: RoomUpgradeServiceProtocol { - // MARK: - Properties // MARK: Private @@ -37,7 +36,7 @@ class RoomUpgradeService: RoomUpgradeServiceProtocol { private(set) var currentRoomId: String var parentSpaceName: String? { - guard let parentId = self.parentSpaceId else { + guard let parentId = parentSpaceId else { return nil } @@ -53,11 +52,11 @@ class RoomUpgradeService: RoomUpgradeServiceProtocol { init(session: MXSession, roomId: String, parentSpaceId: String?, versionOverride: String) { self.session = session - self.currentRoomId = roomId + currentRoomId = roomId self.parentSpaceId = parentSpaceId self.versionOverride = versionOverride - self.upgradingSubject = CurrentValueSubject(false) - self.errorSubject = CurrentValueSubject(nil) + upgradingSubject = CurrentValueSubject(false) + errorSubject = CurrentValueSubject(nil) } deinit { @@ -70,18 +69,18 @@ class RoomUpgradeService: RoomUpgradeServiceProtocol { func upgradeRoom(autoInviteUsers: Bool, completion: @escaping (Bool, String) -> Void) { upgradingSubject.send(true) - if autoInviteUsers, let room = session.room(withRoomId: self.currentRoomId) { - self.currentOperation = room.members { [weak self] response in + if autoInviteUsers, let room = session.room(withRoomId: currentRoomId) { + currentOperation = room.members { [weak self] response in guard let self = self else { return } switch response { case .success(let members): - let memberIds: [String] = members?.members.compactMap({ member in + let memberIds: [String] = members?.members.compactMap { member in guard member.membership == .join, member.userId != self.session.myUserId else { return nil } return member.userId - }) ?? [] + } ?? [] self.upgradeRoom(to: self.versionOverride, inviteUsers: memberIds, completion: completion) case .failure(let error): self.upgradingSubject.send(false) @@ -89,7 +88,7 @@ class RoomUpgradeService: RoomUpgradeServiceProtocol { } } } else { - self.upgradeRoom(to: versionOverride, inviteUsers: [], completion: completion) + upgradeRoom(to: versionOverride, inviteUsers: [], completion: completion) } } @@ -98,7 +97,7 @@ class RoomUpgradeService: RoomUpgradeServiceProtocol { private func upgradeRoom(to versionOverride: String, inviteUsers userIds: [String], completion: @escaping (Bool, String) -> Void) { // Need to disable graph update during this process as a lot of syncs will occure session.spaceService.graphUpdateEnabled = false - currentOperation = session.matrixRestClient.upgradeRoom(withId: self.currentRoomId, to: versionOverride) { [weak self] response in + currentOperation = session.matrixRestClient.upgradeRoom(withId: currentRoomId, to: versionOverride) { [weak self] response in guard let self = self else { return } switch response { @@ -108,7 +107,7 @@ class RoomUpgradeService: RoomUpgradeServiceProtocol { let parentSpaces = self.session.spaceService.directParentIds(ofRoomWithId: oldRoomId) self.moveRoom(from: oldRoomId, to: replacementRoomId, within: Array(parentSpaces), at: 0) { self.session.spaceService.graphUpdateEnabled = true - self.didBuildSpaceGraphObserver = NotificationCenter.default.addObserver(forName: MXSpaceService.didBuildSpaceGraph, object: nil, queue: OperationQueue.main) { [weak self] notification in + self.didBuildSpaceGraphObserver = NotificationCenter.default.addObserver(forName: MXSpaceService.didBuildSpaceGraph, object: nil, queue: OperationQueue.main) { [weak self] _ in guard let self = self else { return } if let observer = self.didBuildSpaceGraphObserver { @@ -144,7 +143,7 @@ class RoomUpgradeService: RoomUpgradeServiceProtocol { } space.moveChild(withRoomId: roomId, to: newRoomId) { [weak self] response in - guard let self = self else { return } + guard let self = self else { return } if let error = response.error { MXLog.warning("[RoomUpgradeService] moveRoom \(roomId) to \(newRoomId) within \(space.spaceId): failed due to error: \(error)") @@ -158,13 +157,13 @@ class RoomUpgradeService: RoomUpgradeServiceProtocol { /// Recurse to the next index once done. private func inviteUser(from userIds: [String], at index: Int, completion: @escaping (Bool, String) -> Void) { guard index < userIds.count else { - self.upgradingSubject.send(false) + upgradingSubject.send(false) completion(true, currentRoomId) return } currentOperation = session.matrixRestClient.invite(.userId(userIds[index]), toRoom: currentRoomId) { [weak self] response in - guard let self = self else { return } + guard let self = self else { return } self.currentOperation = nil if let error = response.error { diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/Mock/MockRoomUpgradeService.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/Mock/MockRoomUpgradeService.swift index a31619875..aed04fc38 100644 --- a/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/Mock/MockRoomUpgradeService.swift +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/Mock/MockRoomUpgradeService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,24 +14,22 @@ // limitations under the License. // -import Foundation import Combine +import Foundation class MockRoomUpgradeService: RoomUpgradeServiceProtocol { - var currentRoomId: String = "!sfdlksjdflkfjds:matrix.org" + var currentRoomId = "!sfdlksjdflkfjds:matrix.org" var errorSubject: CurrentValueSubject var upgradingSubject: CurrentValueSubject var parentSpaceName: String? { - return "Parent space name" + "Parent space name" } init() { - self.errorSubject = CurrentValueSubject(nil) - self.upgradingSubject = CurrentValueSubject(false) + errorSubject = CurrentValueSubject(nil) + upgradingSubject = CurrentValueSubject(false) } - func upgradeRoom(autoInviteUsers: Bool, completion: @escaping (Bool, String) -> Void) { - - } + func upgradeRoom(autoInviteUsers: Bool, completion: @escaping (Bool, String) -> Void) { } } diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/RoomUpgradeServiceProtocol.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/RoomUpgradeServiceProtocol.swift index 49cba3100..20a096d7c 100644 --- a/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/RoomUpgradeServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/RoomUpgradeServiceProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation protocol RoomUpgradeServiceProtocol { var currentRoomId: String { get } diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/UI/RoomUpgradeUITests.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/UI/RoomUpgradeUITests.swift index 5b59c1c5d..011e870ce 100644 --- a/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/UI/RoomUpgradeUITests.swift +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/UI/RoomUpgradeUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class RoomUpgradeUITests: MockScreenTestCase { // Tests to be implemented. diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/Unit/RoomUpgradeViewModelTests.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/Unit/RoomUpgradeViewModelTests.swift index 41cf7f572..19f2cfc72 100644 --- a/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/Unit/RoomUpgradeViewModelTests.swift +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/Unit/RoomUpgradeViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI @@ -24,7 +24,5 @@ class RoomUpgradeViewModelTests: XCTestCase { var viewModel: RoomUpgradeViewModelProtocol! var context: RoomUpgradeViewModelType.Context! - override func setUpWithError() throws { - } - + override func setUpWithError() throws { } } diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/View/RoomUpgrade.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/View/RoomUpgrade.swift index 83eb51991..76c096531 100644 --- a/RiotSwiftUI/Modules/Room/RoomUpgrade/View/RoomUpgrade.swift +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/View/RoomUpgrade.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,10 +17,9 @@ import SwiftUI struct RoomUpgrade: View { - // MARK: - Properties - @State var autoInviteUsers: Bool = true + @State var autoInviteUsers = true // MARK: Private @@ -86,13 +85,13 @@ struct RoomUpgrade: View { .padding(.horizontal, 24) .padding(.vertical, 16) } - .background(RoundedRectangle.init(cornerRadius: 8).foregroundColor(theme.colors.background)) + .background(RoundedRectangle(cornerRadius: 8).foregroundColor(theme.colors.background)) .padding(.horizontal, 20) .frame(minWidth: 0, maxWidth: 500) } private func noteText(_ message: String) -> some View { - return Text(message) + Text(message) .multilineTextAlignment(.center) .font(theme.fonts.subheadline) .foregroundColor(theme.colors.secondaryContent) diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift index 7bdbcb77d..a587b23d8 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift @@ -14,9 +14,9 @@ // limitations under the License. // -import SwiftUI -import MatrixSDK import Combine +import MatrixSDK +import SwiftUI struct TimelinePollCoordinatorParameters { let session: MXSession @@ -25,7 +25,6 @@ struct TimelinePollCoordinatorParameters { } final class TimelinePollCoordinator: Coordinator, Presentable, PollAggregatorDelegate { - // MARK: - Properties // MARK: Private @@ -81,25 +80,24 @@ final class TimelinePollCoordinator: Coordinator, Presentable, PollAggregatorDel } // MARK: - Public - func start() { - - } + + func start() { } func toPresentable() -> UIViewController { - return VectorHostingController(rootView: TimelinePollView(viewModel: viewModel.context), - forceZeroSafeAreaInsets: true) + VectorHostingController(rootView: TimelinePollView(viewModel: viewModel.context), + forceZeroSafeAreaInsets: true) } func canEndPoll() -> Bool { - return pollAggregator.poll.isClosed == false + pollAggregator.poll.isClosed == false } func canEditPoll() -> Bool { - return pollAggregator.poll.isClosed == false && pollAggregator.poll.totalAnswerCount == 0 + pollAggregator.poll.isClosed == false && pollAggregator.poll.totalAnswerCount == 0 } func endPoll() { - parameters.room.sendPollEnd(for: parameters.pollStartEvent, threadId: nil, localEcho: nil, success: nil) { [weak self] error in + parameters.room.sendPollEnd(for: parameters.pollStartEvent, threadId: nil, localEcho: nil, success: nil) { [weak self] _ in self?.viewModel.showClosingFailure() } } @@ -110,17 +108,11 @@ final class TimelinePollCoordinator: Coordinator, Presentable, PollAggregatorDel viewModel.updateWithPollDetails(buildTimelinePollFrom(aggregator.poll)) } - func pollAggregatorDidStartLoading(_ aggregator: PollAggregator) { - - } + func pollAggregatorDidStartLoading(_ aggregator: PollAggregator) { } - func pollAggregatorDidEndLoading(_ aggregator: PollAggregator) { - - } + func pollAggregatorDidEndLoading(_ aggregator: PollAggregator) { } - func pollAggregator(_ aggregator: PollAggregator, didFailWithError: Error) { - - } + func pollAggregator(_ aggregator: PollAggregator, didFailWithError: Error) { } // MARK: - Private @@ -129,19 +121,19 @@ final class TimelinePollCoordinator: Coordinator, Presentable, PollAggregatorDel func buildTimelinePollFrom(_ poll: PollProtocol) -> TimelinePollDetails { let answerOptions = poll.answerOptions.map { pollAnswerOption in TimelinePollAnswerOption(id: pollAnswerOption.id, - text: pollAnswerOption.text, - count: pollAnswerOption.count, - winner: pollAnswerOption.isWinner, - selected: pollAnswerOption.isCurrentUserSelection) + text: pollAnswerOption.text, + count: pollAnswerOption.count, + winner: pollAnswerOption.isWinner, + selected: pollAnswerOption.isCurrentUserSelection) } return TimelinePollDetails(question: poll.text, - answerOptions: answerOptions, - closed: poll.isClosed, - totalAnswerCount: poll.totalAnswerCount, - type: pollKindToTimelinePollType(poll.kind), - maxAllowedSelections: poll.maxAllowedSelections, - hasBeenEdited: poll.hasBeenEdited) + answerOptions: answerOptions, + closed: poll.isClosed, + totalAnswerCount: poll.totalAnswerCount, + type: pollKindToTimelinePollType(poll.kind), + maxAllowedSelections: poll.maxAllowedSelections, + hasBeenEdited: poll.hasBeenEdited) } private func pollKindToTimelinePollType(_ kind: PollKind) -> TimelinePollType { diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift index d40e86757..78b1d8ab7 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,9 +22,7 @@ class TimelinePollProvider { var session: MXSession? var coordinatorsForEventIdentifiers = [String: TimelinePollCoordinator]() - private init() { - - } + private init() { } /// Create or retrieve the poll timeline coordinator for this event and return /// a view to be displayed in the timeline @@ -49,6 +47,6 @@ class TimelinePollProvider { /// Retrieve the poll timeline coordinator for the given event or nil if it hasn't been created yet func timelinePollCoordinatorForEventIdentifier(_ eventIdentifier: String) -> TimelinePollCoordinator? { - return coordinatorsForEventIdentifiers[eventIdentifier] + coordinatorsForEventIdentifiers[eventIdentifier] } } diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/Test/UI/TimelinePollUITests.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/Test/UI/TimelinePollUITests.swift index 98b520ea7..5e7eaceef 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/Test/UI/TimelinePollUITests.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/Test/UI/TimelinePollUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class TimelinePollUITests: MockScreenTestCase { func testOpenDisclosedPoll() { diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/Test/Unit/TimelinePollViewModelTests.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/Test/Unit/TimelinePollViewModelTests.swift index 247f35b0d..0e102dc39 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/Test/Unit/TimelinePollViewModelTests.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/Test/Unit/TimelinePollViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollModels.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollModels.swift index 619088c25..528ad7c17 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollModels.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ import Foundation import SwiftUI -typealias TimelinePollViewModelCallback = ((TimelinePollViewModelResult) -> Void) +typealias TimelinePollViewModelCallback = (TimelinePollViewModelResult) -> Void enum TimelinePollViewAction { case selectAnswerOptionWithIdentifier(String) @@ -63,7 +63,7 @@ struct TimelinePollDetails { var totalAnswerCount: UInt var type: TimelinePollType var maxAllowedSelections: UInt - var hasBeenEdited: Bool = true + var hasBeenEdited = true init(question: String, answerOptions: [TimelinePollAnswerOption], closed: Bool, @@ -81,7 +81,7 @@ struct TimelinePollDetails { } var hasCurrentUserVoted: Bool { - answerOptions.filter { $0.selected == true}.count > 0 + answerOptions.filter { $0.selected == true }.count > 0 } var shouldDiscloseResults: Bool { diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollScreenState.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollScreenState.swift index 0ac107a6e..01fb82c4a 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollScreenState.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,16 +27,16 @@ enum MockTimelinePollScreenState: MockScreenState, CaseIterable { TimelinePollDetails.self } - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let answerOptions = [TimelinePollAnswerOption(id: "1", text: "First", count: 10, winner: false, selected: false), TimelinePollAnswerOption(id: "2", text: "Second", count: 5, winner: false, selected: true), TimelinePollAnswerOption(id: "3", text: "Third", count: 15, winner: true, selected: false)] let poll = TimelinePollDetails(question: "Question", answerOptions: answerOptions, - closed: (self == .closedDisclosed || self == .closedUndisclosed ? true : false), + closed: self == .closedDisclosed || self == .closedUndisclosed ? true : false, totalAnswerCount: 20, - type: (self == .closedDisclosed || self == .openDisclosed ? .disclosed : .undisclosed), + type: self == .closedDisclosed || self == .openDisclosed ? .disclosed : .undisclosed, maxAllowedSelections: 1, hasBeenEdited: false) diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift index 99d15980c..e39ce8cbd 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift @@ -14,14 +14,13 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias TimelinePollViewModelType = StateStoreViewModel + Never, + TimelinePollViewAction> class TimelinePollViewModel: TimelinePollViewModelType, TimelinePollViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -40,14 +39,13 @@ class TimelinePollViewModel: TimelinePollViewModelType, TimelinePollViewModelPro override func process(viewAction: TimelinePollViewAction) { switch viewAction { - // Update local state. An update will be pushed from the coordinator once sent. case .selectAnswerOptionWithIdentifier(let identifier): guard !state.poll.closed else { return } - if (state.poll.maxAllowedSelections == 1) { + if state.poll.maxAllowedSelections == 1 { updateSingleSelectPollLocalState(selectedAnswerIdentifier: identifier, callback: completion) } else { updateMultiSelectPollLocalState(&state, selectedAnswerIdentifier: identifier, callback: completion) @@ -98,12 +96,12 @@ class TimelinePollViewModel: TimelinePollViewModelType, TimelinePollViewModelPro let isDeselecting = selectedAnswerOptions.filter { $0.id == selectedAnswerIdentifier }.count > 0 - if !isDeselecting && selectedAnswerOptions.count >= state.poll.maxAllowedSelections { + if !isDeselecting, selectedAnswerOptions.count >= state.poll.maxAllowedSelections { return } state.poll.answerOptions.updateEach { answerOption in - if (answerOption.id != selectedAnswerIdentifier) { + if answerOption.id != selectedAnswerIdentifier { return } diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModelProtocol.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModelProtocol.swift index 7bd9184eb..492f7f7a3 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollAnswerOptionButton.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollAnswerOptionButton.swift index 5a3498aa9..aaaba7c37 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollAnswerOptionButton.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollAnswerOptionButton.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct TimelinePollAnswerOptionButton: View { - // MARK: - Properties // MARK: Private @@ -47,7 +46,6 @@ struct TimelinePollAnswerOptionButton: View { var answerOptionLabel: some View { VStack(alignment: .leading, spacing: 12.0) { HStack(alignment: .top, spacing: 8.0) { - if !poll.closed { Image(uiImage: answerOption.selected ? Asset.Images.pollCheckboxSelected.image : Asset.Images.pollCheckboxDefault.image) } @@ -56,7 +54,7 @@ struct TimelinePollAnswerOptionButton: View { .font(theme.fonts.body) .foregroundColor(theme.colors.primaryContent) - if poll.closed && answerOption.winner { + if poll.closed, answerOption.winner { Spacer() Image(uiImage: Asset.Images.pollWinnerIcon.image) } @@ -69,7 +67,7 @@ struct TimelinePollAnswerOptionButton: View { .progressViewStyle(LinearProgressViewStyle()) .scaleEffect(x: 1.0, y: 1.2, anchor: .center) - if (poll.shouldDiscloseResults) { + if poll.shouldDiscloseResults { Text(answerOption.count == 1 ? VectorL10n.pollTimelineOneVote : VectorL10n.pollTimelineVotesCount(Int(answerOption.count))) .font(theme.fonts.footnote) .foregroundColor(poll.closed && answerOption.winner ? theme.colors.accent : theme.colors.secondaryContent) @@ -107,33 +105,33 @@ struct TimelinePollAnswerOptionButton_Previews: PreviewProvider { VStack { TimelinePollAnswerOptionButton(poll: buildPoll(closed: false, type: type), answerOption: buildAnswerOption(selected: false), - action: {}) + action: { }) TimelinePollAnswerOptionButton(poll: buildPoll(closed: false, type: type), answerOption: buildAnswerOption(selected: true), - action: {}) + action: { }) TimelinePollAnswerOptionButton(poll: buildPoll(closed: true, type: type), answerOption: buildAnswerOption(selected: false, winner: false), - action: {}) + action: { }) TimelinePollAnswerOptionButton(poll: buildPoll(closed: true, type: type), answerOption: buildAnswerOption(selected: false, winner: true), - action: {}) + action: { }) TimelinePollAnswerOptionButton(poll: buildPoll(closed: true, type: type), answerOption: buildAnswerOption(selected: true, winner: false), - action: {}) + action: { }) TimelinePollAnswerOptionButton(poll: buildPoll(closed: true, type: type), answerOption: buildAnswerOption(selected: true, winner: true), - action: {}) + action: { }) let longText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." TimelinePollAnswerOptionButton(poll: buildPoll(closed: true, type: type), answerOption: buildAnswerOption(text: longText, selected: true, winner: true), - action: {}) + action: { }) } } } @@ -141,12 +139,12 @@ struct TimelinePollAnswerOptionButton_Previews: PreviewProvider { static func buildPoll(closed: Bool, type: TimelinePollType) -> TimelinePollDetails { TimelinePollDetails(question: "", - answerOptions: [], - closed: closed, - totalAnswerCount: 100, - type: type, - maxAllowedSelections: 1, - hasBeenEdited: false) + answerOptions: [], + closed: closed, + totalAnswerCount: 100, + type: type, + maxAllowedSelections: 1, + hasBeenEdited: false) } static func buildAnswerOption(text: String = "Test", selected: Bool, winner: Bool = false) -> TimelinePollAnswerOption { diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollView.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollView.swift index 0b2cf80e8..ff2ce2541 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollView.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollView.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct TimelinePollView: View { - // MARK: - Properties // MARK: Private @@ -32,7 +31,6 @@ struct TimelinePollView: View { let poll = viewModel.viewState.poll VStack(alignment: .leading, spacing: 16.0) { - Text(poll.question) .font(theme.fonts.bodySB) .foregroundColor(theme.colors.primaryContent) + @@ -77,12 +75,12 @@ struct TimelinePollView: View { return VectorL10n.pollTimelineTotalNoVotes case 1: return (poll.hasCurrentUserVoted || poll.type == .undisclosed ? - VectorL10n.pollTimelineTotalOneVote : - VectorL10n.pollTimelineTotalOneVoteNotVoted) + VectorL10n.pollTimelineTotalOneVote : + VectorL10n.pollTimelineTotalOneVoteNotVoted) default: return (poll.hasCurrentUserVoted || poll.type == .undisclosed ? - VectorL10n.pollTimelineTotalVotes(Int(poll.totalAnswerCount)) : - VectorL10n.pollTimelineTotalVotesNotVoted(Int(poll.totalAnswerCount))) + VectorL10n.pollTimelineTotalVotes(Int(poll.totalAnswerCount)) : + VectorL10n.pollTimelineTotalVotesNotVoted(Int(poll.totalAnswerCount))) } } diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift index fd7bd57b3..c3812b5e8 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift @@ -14,10 +14,10 @@ // limitations under the License. // -import Foundation -import UIKit -import SwiftUI import Combine +import Foundation +import SwiftUI +import UIKit protocol UserSuggestionCoordinatorDelegate: AnyObject { func userSuggestionCoordinator(_ coordinator: UserSuggestionCoordinator, didRequestMentionForMember member: MXRoomMember, textTrigger: String?) @@ -30,7 +30,6 @@ struct UserSuggestionCoordinatorParameters { } final class UserSuggestionCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -94,12 +93,11 @@ final class UserSuggestionCoordinator: Coordinator, Presentable { } // MARK: - Public - func start() { - - } + + func start() { } func toPresentable() -> UIViewController { - return self.userSuggestionHostingController + userSuggestionHostingController } // MARK: - Private @@ -131,13 +129,12 @@ final class UserSuggestionCoordinator: Coordinator, Presentable { } private class UserSuggestionCoordinatorRoomMemberProvider: RoomMembersProviderProtocol { - private let room: MXRoom var roomMembers: [MXRoomMember] = [] init(room: MXRoom) { - self.room = room; + self.room = room } func fetchMembers(_ members: @escaping ([RoomMembersProviderMember]) -> Void) { diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinatorBridge.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinatorBridge.swift index 44ddaee9f..c5b68eeee 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinatorBridge.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinatorBridge.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,10 +24,9 @@ protocol UserSuggestionCoordinatorBridgeDelegate: AnyObject { @objcMembers final class UserSuggestionCoordinatorBridge: NSObject { - - private var _userSuggestionCoordinator: Any? = nil + private var _userSuggestionCoordinator: Any? fileprivate var userSuggestionCoordinator: UserSuggestionCoordinator { - return _userSuggestionCoordinator as! UserSuggestionCoordinator + _userSuggestionCoordinator as! UserSuggestionCoordinator } weak var delegate: UserSuggestionCoordinatorBridgeDelegate? @@ -35,7 +34,7 @@ final class UserSuggestionCoordinatorBridge: NSObject { init(mediaManager: MXMediaManager, room: MXRoom) { let parameters = UserSuggestionCoordinatorParameters(mediaManager: mediaManager, room: room) let userSuggestionCoordinator = UserSuggestionCoordinator(parameters: parameters) - self._userSuggestionCoordinator = userSuggestionCoordinator + _userSuggestionCoordinator = userSuggestionCoordinator super.init() @@ -43,11 +42,11 @@ final class UserSuggestionCoordinatorBridge: NSObject { } func processTextMessage(_ textMessage: String) { - return self.userSuggestionCoordinator.processTextMessage(textMessage) + userSuggestionCoordinator.processTextMessage(textMessage) } func toPresentable() -> UIViewController? { - return self.userSuggestionCoordinator.toPresentable() + userSuggestionCoordinator.toPresentable() } } diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionService.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionService.swift index cb135532f..bf8fa00a5 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionService.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation struct RoomMembersProviderMember { var userId: String @@ -34,7 +34,6 @@ struct UserSuggestionServiceItem: UserSuggestionItemProtocol { } class UserSuggestionService: UserSuggestionServiceProtocol { - // MARK: - Properties // MARK: Private @@ -58,7 +57,7 @@ class UserSuggestionService: UserSuggestionServiceProtocol { init(roomMemberProvider: RoomMembersProviderProtocol, shouldDebounce: Bool = true) { self.roomMemberProvider = roomMemberProvider - if (shouldDebounce) { + if shouldDebounce { currentTextTriggerSubject .debounce(for: 0.5, scheduler: RunLoop.main) .removeDuplicates() @@ -79,12 +78,12 @@ class UserSuggestionService: UserSuggestionServiceProtocol { let lastComponent = textMessage.components(separatedBy: .whitespaces).last, lastComponent.prefix(while: { $0 == "@" }).count == 1 // Partial username should start with one and only one "@" character else { - self.items.send([]) - self.currentTextTriggerSubject.send(nil) + items.send([]) + currentTextTriggerSubject.send(nil) return } - self.currentTextTriggerSubject.send(lastComponent) + currentTextTriggerSubject.send(lastComponent) } // MARK: - Private @@ -105,12 +104,12 @@ class UserSuggestionService: UserSuggestionServiceProtocol { UserSuggestionServiceItem(userId: member.userId, displayName: member.displayName, avatarUrl: member.avatarUrl) } - self.items.send(self.suggestionItems.filter({ userSuggestion in + self.items.send(self.suggestionItems.filter { userSuggestion in let containedInUsername = userSuggestion.userId.lowercased().contains(partialName.lowercased()) let containedInDisplayName = (userSuggestion.displayName ?? "").lowercased().contains(partialName.lowercased()) return (containedInUsername || containedInDisplayName) - })) + }) } } } diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionServiceProtocol.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionServiceProtocol.swift index e1ee70a9c..81edb0df9 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionServiceProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation protocol UserSuggestionItemProtocol: Avatarable { var userId: String { get } @@ -24,7 +24,6 @@ protocol UserSuggestionItemProtocol: Avatarable { } protocol UserSuggestionServiceProtocol { - var items: CurrentValueSubject<[UserSuggestionItemProtocol], Never> { get } var currentTextTrigger: String? { get } @@ -38,6 +37,7 @@ extension UserSuggestionItemProtocol { var mxContentUri: String? { avatarUrl } + var matrixItemId: String { userId } diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Test/UI/UserSuggestionUITests.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/Test/UI/UserSuggestionUITests.swift index 53e3c883d..23b204083 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Test/UI/UserSuggestionUITests.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/Test/UI/UserSuggestionUITests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class UserSuggestionUITests: MockScreenTestCase { func testUserSuggestionScreen() throws { diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Test/Unit/UserSuggestionServiceTests.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/Test/Unit/UserSuggestionServiceTests.swift index 552eb4939..b32580c8d 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Test/Unit/UserSuggestionServiceTests.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/Test/Unit/UserSuggestionServiceTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,13 +14,12 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI class UserSuggestionServiceTests: XCTestCase { - var service: UserSuggestionService? override func setUp() { @@ -107,12 +106,11 @@ class UserSuggestionServiceTests: XCTestCase { extension UserSuggestionServiceTests: RoomMembersProviderProtocol { func fetchMembers(_ members: @escaping ([RoomMembersProviderMember]) -> Void) { - let users = [("Alice", "@alice:matrix.org"), ("Bob", "@bob:matrix.org")] - members(users.map({ user in + members(users.map { user in RoomMembersProviderMember(userId: user.1, displayName: user.0, avatarUrl: "") - })) + }) } } diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionModels.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionModels.swift index 8bc107a3c..d4e984f88 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionModels.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionScreenState.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionScreenState.swift index f1b6999f3..f8a8acade 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionScreenState.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,13 +20,13 @@ import SwiftUI enum MockUserSuggestionScreenState: MockScreenState, CaseIterable { case multipleResults - static private var members: [RoomMembersProviderMember]! + private static var members: [RoomMembersProviderMember]! var screenType: Any.Type { UserSuggestionList.self } - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let service = UserSuggestionService(roomMemberProvider: self) let listViewModel = UserSuggestionViewModel(userSuggestionService: service) @@ -37,7 +37,7 @@ enum MockUserSuggestionScreenState: MockScreenState, CaseIterable { return ( [service, listViewModel], AnyView(UserSuggestionListWithInput(viewModel: viewModel) - .addDependency(MockAvatarService.example)) + .addDependency(MockAvatarService.example)) ) } } @@ -52,7 +52,7 @@ extension MockUserSuggestionScreenState: RoomMembersProviderProtocol { } private func generateUsersWithCount(_ count: UInt) -> [RoomMembersProviderMember] { - return (0.. +typealias UserSuggestionViewModelType = StateStoreViewModel class UserSuggestionViewModel: UserSuggestionViewModelType, UserSuggestionViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -39,15 +38,15 @@ class UserSuggestionViewModel: UserSuggestionViewModelType, UserSuggestionViewMo self.userSuggestionService = userSuggestionService let items = userSuggestionService.items.value.map { suggestionItem in - return UserSuggestionViewStateItem(id: suggestionItem.userId, avatar: suggestionItem, displayName: suggestionItem.displayName) + UserSuggestionViewStateItem(id: suggestionItem.userId, avatar: suggestionItem, displayName: suggestionItem.displayName) } super.init(initialViewState: UserSuggestionViewState(items: items)) userSuggestionService.items.sink { [weak self] items in - self?.state.items = items.map({ item in + self?.state.items = items.map { item in UserSuggestionViewStateItem(id: item.userId, avatar: item, displayName: item.displayName) - }) + } }.store(in: &cancellables) } diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModelProtocol.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModelProtocol.swift index 7a04cf8c4..1d89ca9b4 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionList.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionList.swift index 9fc137833..859b0b414 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionList.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionList.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ import SwiftUI struct UserSuggestionList: View { - private struct Constants { + private enum Constants { static let topPadding: CGFloat = 8.0 static let listItemPadding: CGFloat = 4.0 static let lineSpacing: CGFloat = 10.0 @@ -57,7 +57,7 @@ struct UserSuggestionList: View { userId: item.id ) .padding(.bottom, Constants.listItemPadding) - .padding(.top, (viewModel.viewState.items.first?.id == item.id ? Constants.listItemPadding + Constants.topPadding : Constants.listItemPadding)) + .padding(.top, viewModel.viewState.items.first?.id == item.id ? Constants.listItemPadding + Constants.topPadding : Constants.listItemPadding) } } .listStyle(PlainListStyle()) @@ -76,7 +76,6 @@ struct UserSuggestionList: View { } private struct BackgroundView: View { - var content: () -> Content @Environment(\.theme) private var theme: ThemeSwiftUI diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionListItem.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionListItem.swift index c5ade662b..0d3328b33 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionListItem.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionListItem.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,13 +17,14 @@ import SwiftUI struct UserSuggestionListItem: View { - // MARK: - Properties // MARK: Private + @Environment(\.theme) private var theme: ThemeSwiftUI // MARK: Public + let avatar: AvatarInputProtocol? let displayName: String? let userId: String diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionListWithInput.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionListWithInput.swift index cba0fae13..176be8ec4 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionListWithInput.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionListWithInput.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,11 +18,10 @@ import SwiftUI struct UserSuggestionListWithInputViewModel { let listViewModel: UserSuggestionViewModel - let callback: (String)->() + let callback: (String) -> Void } struct UserSuggestionListWithInput: View { - // MARK: - Properties // MARK: Private @@ -30,14 +29,14 @@ struct UserSuggestionListWithInput: View { // MARK: Public var viewModel: UserSuggestionListWithInputViewModel - @State private var inputText: String = "" + @State private var inputText = "" var body: some View { VStack(spacing: 0.0) { UserSuggestionList(viewModel: viewModel.listViewModel.context) TextField("Search for user", text: $inputText) .background(Color.white) - .onChange(of: inputText, perform:viewModel.callback) + .onChange(of: inputText, perform: viewModel.callback) .textFieldStyle(RoundedBorderTextFieldStyle()) .padding([.leading, .trailing]) .onAppear { diff --git a/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordModels.swift b/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordModels.swift index b79c29044..6796dac14 100644 --- a/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordModels.swift +++ b/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,8 +42,8 @@ struct ChangePasswordViewState: BindableState { /// Whether the user can submit the form: old password and new passwords should be entered var canSubmit: Bool { !bindings.oldPassword.isEmpty - && !bindings.newPassword1.isEmpty - && !bindings.newPassword2.isEmpty + && !bindings.newPassword1.isEmpty + && !bindings.newPassword2.isEmpty } } diff --git a/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModel.swift b/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModel.swift index b11309e9a..7882f2eab 100644 --- a/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModel.swift +++ b/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModel.swift @@ -17,10 +17,9 @@ import SwiftUI typealias ChangePasswordViewModelType = StateStoreViewModel + Never, + ChangePasswordViewAction> class ChangePasswordViewModel: ChangePasswordViewModelType, ChangePasswordViewModelProtocol { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModelProtocol.swift b/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModelProtocol.swift index d6050d373..db6848c9c 100644 --- a/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol ChangePasswordViewModelProtocol { - var callback: (@MainActor (ChangePasswordViewModelResult) -> Void)? { get set } var context: ChangePasswordViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Settings/ChangePassword/Coordinator/ChangePasswordBridgePresenter.swift b/RiotSwiftUI/Modules/Settings/ChangePassword/Coordinator/ChangePasswordBridgePresenter.swift index 15c99ade3..e84e61570 100644 --- a/RiotSwiftUI/Modules/Settings/ChangePassword/Coordinator/ChangePasswordBridgePresenter.swift +++ b/RiotSwiftUI/Modules/Settings/ChangePassword/Coordinator/ChangePasswordBridgePresenter.swift @@ -30,7 +30,6 @@ import MatrixSDK /// once the underlying Coordinator has been integrated by another Coordinator. @objcMembers final class ChangePasswordCoordinatorBridgePresenter: NSObject { - // MARK: - Constants // MARK: - Properties @@ -57,8 +56,7 @@ final class ChangePasswordCoordinatorBridgePresenter: NSObject { // MARK: - Public func present(from viewController: UIViewController, animated: Bool) { - - let params = ChangePasswordCoordinatorParameters(restClient: self.session.matrixRestClient) + let params = ChangePasswordCoordinatorParameters(restClient: session.matrixRestClient) let changePasswordCoordinator = ChangePasswordCoordinator(parameters: params) changePasswordCoordinator.callback = { [weak self] in @@ -74,11 +72,11 @@ final class ChangePasswordCoordinatorBridgePresenter: NSObject { viewController.present(navController, animated: animated, completion: nil) changePasswordCoordinator.start() - self.coordinator = changePasswordCoordinator + coordinator = changePasswordCoordinator } func dismiss(animated: Bool, completion: (() -> Void)?) { - guard let coordinator = self.coordinator else { + guard let coordinator = coordinator else { return } diff --git a/RiotSwiftUI/Modules/Settings/ChangePassword/Coordinator/ChangePasswordCoordinator.swift b/RiotSwiftUI/Modules/Settings/ChangePassword/Coordinator/ChangePasswordCoordinator.swift index d582e8721..bd3f6fc29 100644 --- a/RiotSwiftUI/Modules/Settings/ChangePassword/Coordinator/ChangePasswordCoordinator.swift +++ b/RiotSwiftUI/Modules/Settings/ChangePassword/Coordinator/ChangePasswordCoordinator.swift @@ -14,15 +14,14 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI struct ChangePasswordCoordinatorParameters { let restClient: MXRestClient } final class ChangePasswordCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -77,7 +76,7 @@ final class ChangePasswordCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.changePasswordHostingController + changePasswordHostingController } // MARK: - Private diff --git a/RiotSwiftUI/Modules/Settings/ChangePassword/MockChangePasswordScreenState.swift b/RiotSwiftUI/Modules/Settings/ChangePassword/MockChangePasswordScreenState.swift index e4d109f23..e7a176e20 100644 --- a/RiotSwiftUI/Modules/Settings/ChangePassword/MockChangePasswordScreenState.swift +++ b/RiotSwiftUI/Modules/Settings/ChangePassword/MockChangePasswordScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,7 +34,7 @@ enum MockChangePasswordScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel: ChangePasswordViewModel switch self { case .allEmpty: diff --git a/RiotSwiftUI/Modules/Settings/ChangePassword/Test/UI/ChangePasswordUITests.swift b/RiotSwiftUI/Modules/Settings/ChangePassword/Test/UI/ChangePasswordUITests.swift index c96ef6f27..218d6f2c4 100644 --- a/RiotSwiftUI/Modules/Settings/ChangePassword/Test/UI/ChangePasswordUITests.swift +++ b/RiotSwiftUI/Modules/Settings/ChangePassword/Test/UI/ChangePasswordUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class ChangePasswordUITests: MockScreenTestCase { func testAllEmpty() { @@ -125,5 +125,4 @@ class ChangePasswordUITests: MockScreenTestCase { XCTAssertTrue(signoutAllDevicesToggle.exists, "Sign out all devices toggle should exist") XCTAssertTrue(signoutAllDevicesToggle.isOn, "Sign out all devices should be checked") } - } diff --git a/RiotSwiftUI/Modules/Settings/ChangePassword/Test/Unit/ChangePasswordViewModelTests.swift b/RiotSwiftUI/Modules/Settings/ChangePassword/Test/Unit/ChangePasswordViewModelTests.swift index e279c6b53..65f30d16b 100644 --- a/RiotSwiftUI/Modules/Settings/ChangePassword/Test/Unit/ChangePasswordViewModelTests.swift +++ b/RiotSwiftUI/Modules/Settings/ChangePassword/Test/Unit/ChangePasswordViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import XCTest @testable import RiotSwiftUI class ChangePasswordViewModelTests: XCTestCase { - @MainActor func testEmptyState() async { let viewModel = ChangePasswordViewModel() let context = viewModel.context @@ -46,5 +45,4 @@ class ChangePasswordViewModelTests: XCTestCase { XCTAssertTrue(context.viewState.canSubmit, "The view model should be able to submit.") XCTAssertTrue(context.signoutAllDevices, "Sign out of all devices should be checked.") } - } diff --git a/RiotSwiftUI/Modules/Settings/ChangePassword/View/ChangePasswordScreen.swift b/RiotSwiftUI/Modules/Settings/ChangePassword/View/ChangePasswordScreen.swift index ed7ca3dda..0602c3d4d 100644 --- a/RiotSwiftUI/Modules/Settings/ChangePassword/View/ChangePasswordScreen.swift +++ b/RiotSwiftUI/Modules/Settings/ChangePassword/View/ChangePasswordScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct ChangePasswordScreen: View { - // MARK: - Properties // MARK: Private @@ -72,7 +71,7 @@ struct ChangePasswordScreen: View { configuration: UIKitTextInputConfiguration(returnKeyType: .next, isSecureTextEntry: true), onCommit: { focusedField = .newPassword1 }) - .accessibilityIdentifier("oldPasswordTextField") + .accessibilityIdentifier("oldPasswordTextField") RoundedBorderTextField(placeHolder: VectorL10n.settingsNewPassword, text: $viewModel.newPassword1, @@ -80,7 +79,7 @@ struct ChangePasswordScreen: View { configuration: UIKitTextInputConfiguration(returnKeyType: .next, isSecureTextEntry: true), onCommit: { focusedField = .newPassword2 }) - .accessibilityIdentifier("newPasswordTextField1") + .accessibilityIdentifier("newPasswordTextField1") RoundedBorderTextField(placeHolder: VectorL10n.settingsConfirmPassword, text: $viewModel.newPassword2, @@ -88,7 +87,7 @@ struct ChangePasswordScreen: View { configuration: UIKitTextInputConfiguration(returnKeyType: .done, isSecureTextEntry: true), onCommit: submit) - .accessibilityIdentifier("newPasswordTextField2") + .accessibilityIdentifier("newPasswordTextField2") HStack(alignment: .center, spacing: 8) { Toggle(VectorL10n.authenticationChoosePasswordSignoutAllDevices, isOn: $viewModel.signoutAllDevices) diff --git a/RiotSwiftUI/Modules/Settings/Notifications/Coordinator/NotificationSettingsBridgePresenter.swift b/RiotSwiftUI/Modules/Settings/Notifications/Coordinator/NotificationSettingsBridgePresenter.swift index f8a008b90..651ec3224 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/Coordinator/NotificationSettingsBridgePresenter.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/Coordinator/NotificationSettingsBridgePresenter.swift @@ -25,7 +25,6 @@ import Foundation /// Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator. @objcMembers final class NotificationSettingsCoordinatorBridgePresenter: NSObject { - // MARK: - Properties // MARK: Private @@ -48,7 +47,6 @@ final class NotificationSettingsCoordinatorBridgePresenter: NSObject { // MARK: - Public func push(from navigationController: UINavigationController, animated: Bool, screen: NotificationSettingsScreen, popCompletion: (() -> Void)?) { - let router = NavigationRouterStore.shared.navigationRouter(for: navigationController) let notificationSettingsCoordinator = NotificationSettingsCoordinator(session: session, screen: screen) @@ -61,12 +59,12 @@ final class NotificationSettingsCoordinatorBridgePresenter: NSObject { notificationSettingsCoordinator.start() - self.coordinator = notificationSettingsCoordinator + coordinator = notificationSettingsCoordinator self.router = router } func dismiss(animated: Bool, completion: (() -> Void)?) { - guard let coordinator = self.coordinator else { + guard let coordinator = coordinator else { return } coordinator.toPresentable().dismiss(animated: animated) { @@ -80,18 +78,17 @@ final class NotificationSettingsCoordinatorBridgePresenter: NSObject { } // MARK: - NotificationSettingsCoordinatorDelegate + extension NotificationSettingsCoordinatorBridgePresenter: NotificationSettingsCoordinatorDelegate { func notificationSettingsCoordinatorDidComplete(_ coordinator: NotificationSettingsCoordinatorType) { - self.delegate?.notificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self) + delegate?.notificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self) } } // MARK: - UIAdaptivePresentationControllerDelegate extension NotificationSettingsCoordinatorBridgePresenter: UIAdaptivePresentationControllerDelegate { - func notificationSettingsCoordinatorDidComplete(_ presentationController: UIPresentationController) { - self.delegate?.notificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self) + delegate?.notificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self) } - } diff --git a/RiotSwiftUI/Modules/Settings/Notifications/Coordinator/NotificationSettingsCoordinator.swift b/RiotSwiftUI/Modules/Settings/Notifications/Coordinator/NotificationSettingsCoordinator.swift index b903bb103..7ca59854f 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/Coordinator/NotificationSettingsCoordinator.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/Coordinator/NotificationSettingsCoordinator.swift @@ -18,7 +18,6 @@ import Foundation import SwiftUI final class NotificationSettingsCoordinator: NotificationSettingsCoordinatorType { - // MARK: - Properties // MARK: Private @@ -49,24 +48,25 @@ final class NotificationSettingsCoordinator: NotificationSettingsCoordinatorType case .other: viewController = VectorHostingController(rootView: OtherNotificationSettings(viewModel: viewModel)) } - self.notificationSettingsViewModel = viewModel - self.notificationSettingsViewController = viewController + notificationSettingsViewModel = viewModel + notificationSettingsViewController = viewController } // MARK: - Public methods - func start() { - self.notificationSettingsViewModel.coordinatorDelegate = self + func start() { + notificationSettingsViewModel.coordinatorDelegate = self } func toPresentable() -> UIViewController { - return self.notificationSettingsViewController + notificationSettingsViewController } } // MARK: - NotificationSettingsViewModelCoordinatorDelegate + extension NotificationSettingsCoordinator: NotificationSettingsViewModelCoordinatorDelegate { func notificationSettingsViewModelDidComplete(_ viewModel: NotificationSettingsViewModelType) { - self.delegate?.notificationSettingsCoordinatorDidComplete(self) + delegate?.notificationSettingsCoordinatorDidComplete(self) } } diff --git a/RiotSwiftUI/Modules/Settings/Notifications/Model/MatrixSDK/MXNotificationPushRule.swift b/RiotSwiftUI/Modules/Settings/Notifications/Model/MatrixSDK/MXNotificationPushRule.swift index 6c67350af..2c4d16d8b 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/Model/MatrixSDK/MXNotificationPushRule.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/Model/MatrixSDK/MXNotificationPushRule.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,12 +14,11 @@ // limitations under the License. // -import Foundation import DesignKit +import Foundation // Conformance of MXPushRule to the abstraction `NotificationPushRule` for use in `NotificationSettingsViewModel`. extension MXPushRule: NotificationPushRuleType { - /// Given a rule, check it match the actions in the static definition. /// - Parameter standardActions: The standard actions to match against. /// - Returns: Wether `this` rule matches the standard actions. @@ -27,7 +26,7 @@ extension MXPushRule: NotificationPushRuleType { guard let standardActions = standardActions else { return false } - if !enabled && standardActions == .disabled { + if !enabled, standardActions == .disabled { return true } @@ -75,10 +74,10 @@ extension MXPushRule: NotificationPushRuleType { } var notify: Bool { - return getAction(actionType: MXPushRuleActionTypeNotify) != nil + getAction(actionType: MXPushRuleActionTypeNotify) != nil } var dontNotify: Bool { - return getAction(actionType: MXPushRuleActionTypeDontNotify) != nil + getAction(actionType: MXPushRuleActionTypeDontNotify) != nil } } diff --git a/RiotSwiftUI/Modules/Settings/Notifications/Model/Mock/MockNotificationPushRule.swift b/RiotSwiftUI/Modules/Settings/Notifications/Model/Mock/MockNotificationPushRule.swift index 1c83c49f1..49166c99e 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/Model/Mock/MockNotificationPushRule.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/Model/Mock/MockNotificationPushRule.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +20,6 @@ struct MockNotificationPushRule: NotificationPushRuleType { var ruleId: String! var enabled: Bool func matches(standardActions: NotificationStandardActions?) -> Bool { - return false + false } } diff --git a/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationActions.swift b/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationActions.swift index 2facda9cd..88b11b3be 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationActions.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationActions.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationIndex.swift b/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationIndex.swift index 89088159a..b4252ebbd 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationIndex.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationIndex.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,11 +33,11 @@ extension NotificationIndex { /// - Parameter enabled: Enabled/Disabled state. /// - Returns: The associated NotificationIndex static func index(when enabled: Bool) -> NotificationIndex { - return enabled ? .noisy : .off + enabled ? .noisy : .off } /// Used to map from the checked state back to the index. var enabled: Bool { - return self != .off + self != .off } } diff --git a/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationPushRuleDefinitions.swift b/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationPushRuleDefinitions.swift index 10fa5ec90..77c6e1ef2 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationPushRuleDefinitions.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationPushRuleDefinitions.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +16,6 @@ import Foundation - extension NotificationPushRuleId { /// A static definition of the push rule actions. /// diff --git a/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationPushRuleIds.swift b/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationPushRuleIds.swift index 38ed2b521..d74968c8b 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationPushRuleIds.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationPushRuleIds.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,7 +32,6 @@ enum NotificationPushRuleId: String { case keywords = "_keywords" } - extension NotificationPushRuleId: Identifiable { var id: String { rawValue diff --git a/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationPushRuleType.swift b/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationPushRuleType.swift index 5f58befad..1f98242c7 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationPushRuleType.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationPushRuleType.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationSettingsScreen.swift b/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationSettingsScreen.swift index 7049e67bd..fce506096 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationSettingsScreen.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationSettingsScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,7 +26,7 @@ import Foundation extension NotificationSettingsScreen: CaseIterable { } extension NotificationSettingsScreen: Identifiable { - var id: Int { self.rawValue } + var id: Int { rawValue } } extension NotificationSettingsScreen { diff --git a/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationStandardActions.swift b/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationStandardActions.swift index 466b11595..89b8a55b5 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationStandardActions.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationStandardActions.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Settings/Notifications/Service/MatrixSDK/MXNotificationSettingsService.swift b/RiotSwiftUI/Modules/Settings/Notifications/Service/MatrixSDK/MXNotificationSettingsService.swift index e7ac7a191..375b50ab9 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/Service/MatrixSDK/MXNotificationSettingsService.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/Service/MatrixSDK/MXNotificationSettingsService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,11 +14,10 @@ // limitations under the License. // -import Foundation import Combine +import Foundation class MXNotificationSettingsService: NotificationSettingsServiceType { - private let session: MXSession private var cancellables = Set() @@ -26,11 +25,11 @@ class MXNotificationSettingsService: NotificationSettingsServiceType { @Published private var rules = [MXPushRule]() var rulesPublisher: AnyPublisher<[NotificationPushRuleType], Never> { - $rules.map({ $0.map({ $0 as NotificationPushRuleType }) }).eraseToAnyPublisher() + $rules.map { $0.map { $0 as NotificationPushRuleType } }.eraseToAnyPublisher() } var contentRulesPublisher: AnyPublisher<[NotificationPushRuleType], Never> { - $contentRules.map({ $0.map({ $0 as NotificationPushRuleType }) }).eraseToAnyPublisher() + $contentRules.map { $0.map { $0 as NotificationPushRuleType } }.eraseToAnyPublisher() } init(session: MXSession) { @@ -45,7 +44,7 @@ class MXNotificationSettingsService: NotificationSettingsServiceType { // Observe future updates to content rules rulesUpdated - .compactMap({ _ in self.session.notificationCenter.rules.global.content as? [MXPushRule] }) + .compactMap { _ in self.session.notificationCenter.rules.global.content as? [MXPushRule] } .assign(to: &$contentRules) // Set initial value of rules @@ -54,7 +53,7 @@ class MXNotificationSettingsService: NotificationSettingsServiceType { } // Observe future updates to rules rulesUpdated - .compactMap({ _ in self.session.notificationCenter.flatRules as? [MXPushRule] }) + .compactMap { _ in self.session.notificationCenter.flatRules as? [MXPushRule] } .assign(to: &$rules) } diff --git a/RiotSwiftUI/Modules/Settings/Notifications/Service/Mock/MockNotificationSettingsService.swift b/RiotSwiftUI/Modules/Settings/Notifications/Service/Mock/MockNotificationSettingsService.swift index a7faceb52..44a553f6c 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/Service/Mock/MockNotificationSettingsService.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/Service/Mock/MockNotificationSettingsService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation class MockNotificationSettingsService: NotificationSettingsServiceType, ObservableObject { static let example = MockNotificationSettingsService() @@ -44,7 +44,5 @@ class MockNotificationSettingsService: NotificationSettingsServiceType, Observab keywords.remove(keyword) } - func updatePushRuleActions(for ruleId: String, enabled: Bool, actions: NotificationActions?) { - - } + func updatePushRuleActions(for ruleId: String, enabled: Bool, actions: NotificationActions?) { } } diff --git a/RiotSwiftUI/Modules/Settings/Notifications/Service/NotificationSettingsServiceType.swift b/RiotSwiftUI/Modules/Settings/Notifications/Service/NotificationSettingsServiceType.swift index 6b0565ea6..a5a1671e3 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/Service/NotificationSettingsServiceType.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/Service/NotificationSettingsServiceType.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation /// A service for changing notification settings and keywords protocol NotificationSettingsServiceType { diff --git a/RiotSwiftUI/Modules/Settings/Notifications/View/Chip.swift b/RiotSwiftUI/Modules/Settings/Notifications/View/Chip.swift index 0c9bfd088..24e800acb 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/View/Chip.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/View/Chip.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,10 +16,8 @@ import SwiftUI - /// A single rounded rect chip to be rendered within `Chips` collection struct Chip: View { - @Environment(\.isEnabled) var isEnabled @Environment(\.theme) var theme: ThemeSwiftUI @@ -57,7 +55,6 @@ struct Chip: View { .background(backgroundColor) .foregroundColor(foregroundColor) .cornerRadius(20) - } } diff --git a/RiotSwiftUI/Modules/Settings/Notifications/View/Chips.swift b/RiotSwiftUI/Modules/Settings/Notifications/View/Chips.swift index 9b66584d1..caccfad80 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/View/Chips.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/View/Chips.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,7 @@ import SwiftUI /// Renders multiple chips in a flow layout. struct Chips: View { - - @State private var frame: CGRect = CGRect.zero + @State private var frame = CGRect.zero let titles: [String] let didDeleteChip: (String) -> Void @@ -56,7 +55,7 @@ struct Chips: View { } return result } - .alignmentGuide(.top) { dimension in + .alignmentGuide(.top) { _ in // Use next y value and reset if its the last. let result = y if chip == titles.last { @@ -82,6 +81,5 @@ struct Chips_Previews: PreviewProvider { Chips(titles: chips, didDeleteChip: { _ in }) .theme(.dark) } - } } diff --git a/RiotSwiftUI/Modules/Settings/Notifications/View/ChipsInput.swift b/RiotSwiftUI/Modules/Settings/Notifications/View/ChipsInput.swift index 5c62ddd34..e913447da 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/View/ChipsInput.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/View/ChipsInput.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,17 +18,15 @@ import SwiftUI /// Renders an input field and a collection of chips. struct ChipsInput: View { - @Environment(\.theme) var theme: ThemeSwiftUI @Environment(\.isEnabled) var isEnabled - @State private var chipText: String = "" + @State private var chipText = "" let titles: [String] let didAddChip: (String) -> Void let didDeleteChip: (String) -> Void - var placeholder: String = "" - + var placeholder = "" var body: some View { VStack(spacing: 16) { @@ -55,6 +53,5 @@ struct ChipsInput_Previews: PreviewProvider { chips.remove(chip) } .disabled(true) - } } diff --git a/RiotSwiftUI/Modules/Settings/Notifications/View/DefaultNotificationSettings.swift b/RiotSwiftUI/Modules/Settings/Notifications/View/DefaultNotificationSettings.swift index 8dd937614..5fecd7e29 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/View/DefaultNotificationSettings.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/View/DefaultNotificationSettings.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct DefaultNotificationSettings: View { - @ObservedObject var viewModel: NotificationSettingsViewModel var body: some View { @@ -38,6 +37,5 @@ struct DefaultNotifications_Previews: PreviewProvider { ) .navigationBarTitleDisplayMode(.inline) } - } } diff --git a/RiotSwiftUI/Modules/Settings/Notifications/View/FormInputFieldStyle.swift b/RiotSwiftUI/Modules/Settings/Notifications/View/FormInputFieldStyle.swift index 3b35cd396..7232dd2c7 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/View/FormInputFieldStyle.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/View/FormInputFieldStyle.swift @@ -19,7 +19,6 @@ import SwiftUI /// An input field style for forms. struct FormInputFieldStyle: TextFieldStyle { - @Environment(\.theme) var theme: ThemeSwiftUI @Environment(\.isEnabled) var isEnabled @@ -31,7 +30,7 @@ struct FormInputFieldStyle: TextFieldStyle { } private var backgroundColor: Color { - if !isEnabled && theme.identifier == .dark { + if !isEnabled, theme.identifier == .dark { return theme.colors.quinaryContent } return theme.colors.background @@ -47,7 +46,6 @@ struct FormInputFieldStyle: TextFieldStyle { } } - struct FormInputFieldStyle_Previews: PreviewProvider { static var previews: some View { Group { @@ -59,7 +57,6 @@ struct FormInputFieldStyle_Previews: PreviewProvider { TextField("Placeholder", text: .constant("Web")) .textFieldStyle(FormInputFieldStyle()) .disabled(true) - } .padding() VectorForm { @@ -70,11 +67,9 @@ struct FormInputFieldStyle_Previews: PreviewProvider { TextField("Placeholder", text: .constant("Web")) .textFieldStyle(FormInputFieldStyle()) .disabled(true) - } .padding() .theme(ThemeIdentifier.dark) } - } } diff --git a/RiotSwiftUI/Modules/Settings/Notifications/View/MentionsAndKeywordNotificationSettings.swift b/RiotSwiftUI/Modules/Settings/Notifications/View/MentionsAndKeywordNotificationSettings.swift index 78a4d733c..ac1316bfd 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/View/MentionsAndKeywordNotificationSettings.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/View/MentionsAndKeywordNotificationSettings.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct MentionsAndKeywordNotificationSettings: View { - @ObservedObject var viewModel: NotificationSettingsViewModel var keywordSection: some View { @@ -28,6 +27,7 @@ struct MentionsAndKeywordNotificationSettings: View { NotificationSettingsKeywords(viewModel: viewModel) } } + var body: some View { NotificationSettings( viewModel: viewModel, diff --git a/RiotSwiftUI/Modules/Settings/Notifications/View/NotificationSettings.swift b/RiotSwiftUI/Modules/Settings/Notifications/View/NotificationSettings.swift index 6943009ce..18be2680d 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/View/NotificationSettings.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/View/NotificationSettings.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,6 @@ import SwiftUI /// Also renders an optional bottom section. /// Used in the case of keywords, for the keyword chips and input. struct NotificationSettings: View { - @ObservedObject var viewModel: NotificationSettingsViewModel var bottomSection: BottomSection? diff --git a/RiotSwiftUI/Modules/Settings/Notifications/View/NotificationSettingsKeywords.swift b/RiotSwiftUI/Modules/Settings/Notifications/View/NotificationSettingsKeywords.swift index 6c358f555..a83980710 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/View/NotificationSettingsKeywords.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/View/NotificationSettingsKeywords.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,7 +27,6 @@ struct NotificationSettingsKeywords: View { placeholder: VectorL10n.settingsNewKeyword ) .disabled(!(viewModel.viewState.selectionState[.keywords] ?? false)) - } } diff --git a/RiotSwiftUI/Modules/Settings/Notifications/View/OtherNotificationSettings.swift b/RiotSwiftUI/Modules/Settings/Notifications/View/OtherNotificationSettings.swift index 5536a662b..ba906fc13 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/View/OtherNotificationSettings.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/View/OtherNotificationSettings.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Settings/Notifications/ViewModel/NotificationSettingsViewModel.swift b/RiotSwiftUI/Modules/Settings/Notifications/ViewModel/NotificationSettingsViewModel.swift index ad1af1014..588597572 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/ViewModel/NotificationSettingsViewModel.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/ViewModel/NotificationSettingsViewModel.swift @@ -16,12 +16,11 @@ limitations under the License. */ -import Foundation import Combine +import Foundation import SwiftUI final class NotificationSettingsViewModel: NotificationSettingsViewModelType, ObservableObject { - // MARK: - Properties // MARK: Private @@ -46,7 +45,7 @@ final class NotificationSettingsViewModel: NotificationSettingsViewModelType, Ob init(notificationSettingsService: NotificationSettingsServiceType, ruleIds: [NotificationPushRuleId], initialState: NotificationSettingsViewState) { self.notificationSettingsService = notificationSettingsService self.ruleIds = ruleIds - self.viewState = initialState + viewState = initialState // Observe when the rules are updated, to subsequently update the state of the settings. notificationSettingsService.rulesPublisher @@ -57,11 +56,11 @@ final class NotificationSettingsViewModel: NotificationSettingsViewModelType, Ob if ruleIds.contains(.keywords) { // Publisher of all the keyword push rules (keyword rules do not start with '.') let keywordsRules = notificationSettingsService.contentRulesPublisher - .map { $0.filter { !$0.ruleId.starts(with: ".")} } + .map { $0.filter { !$0.ruleId.starts(with: ".") } } // Map to just the keyword strings let keywords = keywordsRules - .map { Set($0.compactMap { $0.ruleId }) } + .map { Set($0.compactMap(\.ruleId)) } // Update the keyword set keywords @@ -100,7 +99,7 @@ final class NotificationSettingsViewModel: NotificationSettingsViewModelType, Ob } convenience init(notificationSettingsService: NotificationSettingsServiceType, ruleIds: [NotificationPushRuleId]) { - let ruleState = Dictionary(uniqueKeysWithValues: ruleIds.map({ ($0, selected: true) })) + let ruleState = Dictionary(uniqueKeysWithValues: ruleIds.map { ($0, selected: true) }) self.init(notificationSettingsService: notificationSettingsService, ruleIds: ruleIds, initialState: NotificationSettingsViewState(saving: false, ruleIds: ruleIds, selectionState: ruleState)) } @@ -125,7 +124,7 @@ final class NotificationSettingsViewModel: NotificationSettingsViewModelType, Ob private func updateKeywords(isChecked: Bool) { guard !keywordsOrdered.isEmpty else { - self.viewState.selectionState[.keywords]?.toggle() + viewState.selectionState[.keywords]?.toggle() return } // Get the static definition and update the actions and enabled state for every keyword. @@ -149,22 +148,23 @@ final class NotificationSettingsViewModel: NotificationSettingsViewModelType, Ob } func remove(keyword: String) { - keywordsOrdered = keywordsOrdered.filter({ $0 != keyword }) + keywordsOrdered = keywordsOrdered.filter { $0 != keyword } notificationSettingsService.remove(keyword: keyword) } // MARK: - Private + private func rulesUpdated(newRules: [NotificationPushRuleType]) { for rule in newRules { guard let ruleId = NotificationPushRuleId(rawValue: rule.ruleId), ruleIds.contains(ruleId) else { continue } - self.viewState.selectionState[ruleId] = self.isChecked(rule: rule) + viewState.selectionState[ruleId] = isChecked(rule: rule) } } private func keywordRuleUpdated(anyEnabled: Bool) { if !keywordsOrdered.isEmpty { - self.viewState.selectionState[.keywords] = anyEnabled + viewState.selectionState[.keywords] = anyEnabled } } @@ -178,7 +178,7 @@ final class NotificationSettingsViewModel: NotificationSettingsViewModelType, Ob guard let ruleId = NotificationPushRuleId(rawValue: rule.ruleId) else { return false } let firstIndex = NotificationIndex.allCases.first { nextIndex in - return rule.matches(standardActions: ruleId.standardActions(for: nextIndex)) + rule.matches(standardActions: ruleId.standardActions(for: nextIndex)) } guard let index = firstIndex else { @@ -187,5 +187,4 @@ final class NotificationSettingsViewModel: NotificationSettingsViewModelType, Ob return index.enabled } - } diff --git a/RiotSwiftUI/Modules/Spaces/AddRoomSelector/Coordinator/AddRoomSelectorViewProvider.swift b/RiotSwiftUI/Modules/Spaces/AddRoomSelector/Coordinator/AddRoomSelectorViewProvider.swift index c24d5b6ed..af1fe6ee6 100644 --- a/RiotSwiftUI/Modules/Spaces/AddRoomSelector/Coordinator/AddRoomSelectorViewProvider.swift +++ b/RiotSwiftUI/Modules/Spaces/AddRoomSelector/Coordinator/AddRoomSelectorViewProvider.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,6 @@ import SwiftUI class AddRoomSelectorViewProvider: MatrixItemChooserCoordinatorViewProvider { func view(with viewModel: MatrixItemChooserViewModelType.Context) -> AnyView { - return AnyView(AddRoomSelector(viewModel: viewModel)) + AnyView(AddRoomSelector(viewModel: viewModel)) } } diff --git a/RiotSwiftUI/Modules/Spaces/AddRoomSelector/Service/MatrixSDK/AddRoomItemsProcessor.swift b/RiotSwiftUI/Modules/Spaces/AddRoomSelector/Service/MatrixSDK/AddRoomItemsProcessor.swift index 2696ee49b..ec2ad692c 100644 --- a/RiotSwiftUI/Modules/Spaces/AddRoomSelector/Service/MatrixSDK/AddRoomItemsProcessor.swift +++ b/RiotSwiftUI/Modules/Spaces/AddRoomSelector/Service/MatrixSDK/AddRoomItemsProcessor.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation class AddRoomItemsProcessor: MatrixItemChooserProcessorProtocol { - // MARK: Private private let parentSpace: MXSpace @@ -40,14 +39,14 @@ class AddRoomItemsProcessor: MatrixItemChooserProcessorProtocol { addChild(from: itemsIds, at: 0, completion: completion) } - func isItemIncluded(_ item: (MatrixListItemData)) -> Bool { - return !parentSpace.isRoomAChild(roomId: item.id) + func isItemIncluded(_ item: MatrixListItemData) -> Bool { + !parentSpace.isRoomAChild(roomId: item.id) } // MARK: Private /// Add room with roomId from list of room IDs at index to the parentSpace. - /// Recurse to the next index once done. + /// Recurse to the next index once done. func addChild(from roomIds: [String], at index: Int, completion: @escaping (Result) -> Void) { guard index < roomIds.count else { // last item has been processed or list is empty --> the recursion has finished diff --git a/RiotSwiftUI/Modules/Spaces/AddRoomSelector/View/AddRoomSelector.swift b/RiotSwiftUI/Modules/Spaces/AddRoomSelector/View/AddRoomSelector.swift index adfc48471..3babc259b 100644 --- a/RiotSwiftUI/Modules/Spaces/AddRoomSelector/View/AddRoomSelector.swift +++ b/RiotSwiftUI/Modules/Spaces/AddRoomSelector/View/AddRoomSelector.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct AddRoomSelector: View { - // MARK: Properties @ObservedObject var viewModel: MatrixItemChooserViewModel.Context @@ -26,7 +25,7 @@ struct AddRoomSelector: View { @Environment(\.theme) private var theme: ThemeSwiftUI private var isDoneEnabled: Bool { - return !viewModel.viewState.selectedItemIds.isEmpty && !viewModel.viewState.loading + !viewModel.viewState.selectedItemIds.isEmpty && !viewModel.viewState.loading } // MARK: Setup diff --git a/RiotSwiftUI/Modules/Spaces/LeaveSpace/Coordinator/LeaveSpaceViewProvider.swift b/RiotSwiftUI/Modules/Spaces/LeaveSpace/Coordinator/LeaveSpaceViewProvider.swift index e1e5df850..ced508155 100644 --- a/RiotSwiftUI/Modules/Spaces/LeaveSpace/Coordinator/LeaveSpaceViewProvider.swift +++ b/RiotSwiftUI/Modules/Spaces/LeaveSpace/Coordinator/LeaveSpaceViewProvider.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI class LeaveSpaceViewProvider: MatrixItemChooserCoordinatorViewProvider { - private let navTitle: String? init(navTitle: String?) { @@ -25,6 +24,6 @@ class LeaveSpaceViewProvider: MatrixItemChooserCoordinatorViewProvider { } func view(with viewModel: MatrixItemChooserViewModelType.Context) -> AnyView { - return AnyView(LeaveSpace(viewModel: viewModel, navTitle: navTitle)) + AnyView(LeaveSpace(viewModel: viewModel, navTitle: navTitle)) } } diff --git a/RiotSwiftUI/Modules/Spaces/LeaveSpace/Service/MatrixSDK/LeaveSpaceItemsProcessor.swift b/RiotSwiftUI/Modules/Spaces/LeaveSpace/Service/MatrixSDK/LeaveSpaceItemsProcessor.swift index 912ad551d..3fb4cbb99 100644 --- a/RiotSwiftUI/Modules/Spaces/LeaveSpace/Service/MatrixSDK/LeaveSpaceItemsProcessor.swift +++ b/RiotSwiftUI/Modules/Spaces/LeaveSpace/Service/MatrixSDK/LeaveSpaceItemsProcessor.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ import Foundation import MatrixSDK class LeaveSpaceItemsProcessor: MatrixItemChooserProcessorProtocol { - // MARK: Private private let spaceId: String @@ -29,7 +28,7 @@ class LeaveSpaceItemsProcessor: MatrixItemChooserProcessorProtocol { init(spaceId: String, session: MXSession) { self.spaceId = spaceId self.session = session - self.dataSource = MatrixItemChooserDirectChildrenDataSource(parentId: spaceId) + dataSource = MatrixItemChooserDirectChildrenDataSource(parentId: spaceId) } // MARK: MatrixItemChooserSelectionProcessorProtocol @@ -41,11 +40,11 @@ class LeaveSpaceItemsProcessor: MatrixItemChooserProcessorProtocol { } func computeSelection(withIds itemsIds: [String], completion: @escaping (Result) -> Void) { - guard let space = self.session.spaceService.getSpace(withId: self.spaceId) else { + guard let space = session.spaceService.getSpace(withId: spaceId) else { return } - self.leaveAllRooms(from: itemsIds, at: 0) { [weak self] result in + leaveAllRooms(from: itemsIds, at: 0) { [weak self] result in guard let self = self else { return } switch result { @@ -57,8 +56,8 @@ class LeaveSpaceItemsProcessor: MatrixItemChooserProcessorProtocol { } } - func isItemIncluded(_ item: (MatrixListItemData)) -> Bool { - return true + func isItemIncluded(_ item: MatrixListItemData) -> Bool { + true } // MARK: Private @@ -71,8 +70,8 @@ class LeaveSpaceItemsProcessor: MatrixItemChooserProcessorProtocol { return } - guard let room = self.session.room(withRoomId: roomIds[index]), !room.isDirect else { - self.leaveAllRooms(from: roomIds, at: index+1, completion: completion) + guard let room = session.room(withRoomId: roomIds[index]), !room.isDirect else { + leaveAllRooms(from: roomIds, at: index + 1, completion: completion) return } @@ -82,7 +81,7 @@ class LeaveSpaceItemsProcessor: MatrixItemChooserProcessorProtocol { switch response { case .success: - self.leaveAllRooms(from: roomIds, at: index+1, completion: completion) + self.leaveAllRooms(from: roomIds, at: index + 1, completion: completion) case .failure(let error): MXLog.error("[LeaveSpaceItemsProcessor] failed to leave room", context: error) completion(.failure(error)) diff --git a/RiotSwiftUI/Modules/Spaces/LeaveSpace/View/LeaveSpace.swift b/RiotSwiftUI/Modules/Spaces/LeaveSpace/View/LeaveSpace.swift index da065f36f..4de0a5af6 100644 --- a/RiotSwiftUI/Modules/Spaces/LeaveSpace/View/LeaveSpace.swift +++ b/RiotSwiftUI/Modules/Spaces/LeaveSpace/View/LeaveSpace.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct LeaveSpace: View { - // MARK: Properties @ObservedObject var viewModel: MatrixItemChooserViewModel.Context @@ -53,5 +52,4 @@ struct LeaveSpace: View { .buttonStyle(PrimaryActionButtonStyle(customColor: theme.colors.alert)) .padding() } - } diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Coordinator/MatrixItemChooserCoordinator.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Coordinator/MatrixItemChooserCoordinator.swift index c8a632ada..467c69eb9 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Coordinator/MatrixItemChooserCoordinator.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Coordinator/MatrixItemChooserCoordinator.swift @@ -15,8 +15,8 @@ // import Foundation -import UIKit import SwiftUI +import UIKit internal protocol MatrixItemChooserCoordinatorViewProvider { func view(with viewModel: MatrixItemChooserViewModelType.Context) -> AnyView @@ -49,7 +49,6 @@ struct MatrixItemChooserCoordinatorParameters { } final class MatrixItemChooserCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -94,6 +93,6 @@ final class MatrixItemChooserCoordinator: Coordinator, Presentable { // MARK: - Presentable func toPresentable() -> UIViewController { - return self.matrixItemChooserHostingController + matrixItemChooserHostingController } } diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserModels.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserModels.swift index da1374498..3493ae131 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserModels.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -58,7 +58,7 @@ struct MatrixListItemSectionData { } } -extension MatrixListItemSectionData: Identifiable, Equatable {} +extension MatrixListItemSectionData: Identifiable, Equatable { } struct MatrixListItemData { let id: String @@ -68,7 +68,7 @@ struct MatrixListItemData { let detailText: String? } -extension MatrixListItemData: Identifiable, Equatable {} +extension MatrixListItemData: Identifiable, Equatable { } struct MatrixItemChooserSelectionHeader { var title: String diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModel.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModel.swift index 9aa6786c8..0e487565e 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModel.swift @@ -14,21 +14,20 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias MatrixItemChooserViewModelType = StateStoreViewModel + Never, + MatrixItemChooserViewAction> class MatrixItemChooserViewModel: MatrixItemChooserViewModelType, MatrixItemChooserViewModelProtocol { - // MARK: - Properties // MARK: Private private var matrixItemChooserService: MatrixItemChooserServiceProtocol - private var isLoading: Bool = false { + private var isLoading = false { didSet { state.loading = isLoading if isLoading { @@ -44,7 +43,7 @@ class MatrixItemChooserViewModel: MatrixItemChooserViewModelType, MatrixItemChoo // MARK: - Setup static func makeMatrixItemChooserViewModel(matrixItemChooserService: MatrixItemChooserServiceProtocol, title: String?, detail: String?, selectionHeader: MatrixItemChooserSelectionHeader?) -> MatrixItemChooserViewModelProtocol { - return MatrixItemChooserViewModel(matrixItemChooserService: matrixItemChooserService, title: title, detail: detail, selectionHeader: selectionHeader) + MatrixItemChooserViewModel(matrixItemChooserService: matrixItemChooserService, title: title, detail: detail, selectionHeader: selectionHeader) } private init(matrixItemChooserService: MatrixItemChooserServiceProtocol, title: String?, detail: String?, selectionHeader: MatrixItemChooserSelectionHeader?) { @@ -98,13 +97,13 @@ class MatrixItemChooserViewModel: MatrixItemChooserViewModelType, MatrixItemChoo } } case .searchTextChanged(let searchText): - self.matrixItemChooserService.searchText = searchText + matrixItemChooserService.searchText = searchText case .itemTapped(let itemId): - self.matrixItemChooserService.reverseSelectionForItem(withId: itemId) + matrixItemChooserService.reverseSelectionForItem(withId: itemId) case .selectAll: - self.matrixItemChooserService.selectAllItems() + matrixItemChooserService.selectAllItems() case .selectNone: - self.matrixItemChooserService.deselectAllItems() + matrixItemChooserService.deselectAllItems() } } @@ -119,5 +118,4 @@ class MatrixItemChooserViewModel: MatrixItemChooserViewModelType, MatrixItemChoo private func back() { completion?(.back) } - } diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModelProtocol.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModelProtocol.swift index 3bf385f7f..d9e7899e9 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol MatrixItemChooserViewModelProtocol { - var completion: ((MatrixItemChooserViewModelResult) -> Void)? { get set } static func makeMatrixItemChooserViewModel(matrixItemChooserService: MatrixItemChooserServiceProtocol, title: String?, detail: String?, selectionHeader: MatrixItemChooserSelectionHeader?) -> MatrixItemChooserViewModelProtocol var context: MatrixItemChooserViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MockMatrixItemChooserScreenState.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MockMatrixItemChooserScreenState.swift index 8b32c9fb5..e18476c24 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MockMatrixItemChooserScreenState.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MockMatrixItemChooserScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixItemChooserServiceProtocol.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixItemChooserServiceProtocol.swift index 88218ff79..ce396d993 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixItemChooserServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixItemChooserServiceProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation protocol MatrixItemChooserServiceProtocol { var sectionsSubject: CurrentValueSubject<[MatrixListItemSectionData], Never> { get } diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserDirectChildrenDataSource.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserDirectChildrenDataSource.swift index c48d6abdb..1a359d60c 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserDirectChildrenDataSource.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserDirectChildrenDataSource.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation class MatrixItemChooserDirectChildrenDataSource: MatrixItemChooserDataSource { - // MARK: - Private private let parentId: String @@ -34,13 +33,13 @@ class MatrixItemChooserDirectChildrenDataSource: MatrixItemChooserDataSource { func sections(with session: MXSession, completion: @escaping (Result<[MatrixListItemSectionData], Error>) -> Void) { let space = session.spaceService.getSpace(withId: parentId) - let children: [MatrixListItemData] = space?.childRoomIds.compactMap({ roomId in + let children: [MatrixListItemData] = space?.childRoomIds.compactMap { roomId in guard let room = session.room(withRoomId: roomId), !room.isDirect else { return nil } return MatrixListItemData(mxRoom: room, spaceService: session.spaceService) - }) ?? [] + } ?? [] completion(Result(catching: { [ MatrixListItemSectionData(items: children) diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomAncestorsDataSource.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomAncestorsDataSource.swift index aee17961b..d3ee2bee5 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomAncestorsDataSource.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomAncestorsDataSource.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,7 @@ class MatrixItemChooserRoomAncestorsDataSource: MatrixItemChooserDataSource { func sections(with session: MXSession, completion: @escaping (Result<[MatrixListItemSectionData], Error>) -> Void) { let ancestorsIds = session.spaceService.ancestorsPerRoomId[roomId] ?? [] completion(Result(catching: { - return [ + [ MatrixListItemSectionData(title: VectorL10n.roomAccessSpaceChooserKnownSpacesSection(session.room(withRoomId: roomId)?.displayName ?? ""), items: ancestorsIds.compactMap { spaceId in guard let space = session.spaceService.getSpace(withId: spaceId) else { return nil diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomDirectParentsDataSource.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomDirectParentsDataSource.swift index 95e292356..dade8ced2 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomDirectParentsDataSource.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomDirectParentsDataSource.swift @@ -22,7 +22,6 @@ enum MatrixItemChooserRoomDirectParentsDataSourcePreselectionMode { } class MatrixItemChooserRoomDirectParentsDataSource: MatrixItemChooserDataSource { - private let roomId: String private let preselectionMode: MatrixItemChooserRoomDirectParentsDataSourcePreselectionMode @@ -44,7 +43,7 @@ class MatrixItemChooserRoomDirectParentsDataSource: MatrixItemChooserDataSource } completion(Result(catching: { - return [ + [ MatrixListItemSectionData(title: VectorL10n.roomAccessSpaceChooserKnownSpacesSection(session.room(withRoomId: roomId)?.displayName ?? ""), items: ancestorsIds.compactMap { spaceId in guard let space = session.spaceService.getSpace(withId: spaceId) else { return nil diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomRestrictedAllowedParentsDataSource.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomRestrictedAllowedParentsDataSource.swift index 4f4443c44..999d96cb9 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomRestrictedAllowedParentsDataSource.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomRestrictedAllowedParentsDataSource.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,7 +34,7 @@ class MatrixItemChooserRoomRestrictedAllowedParentsDataSource: MatrixItemChooser } room.state { [weak self] state in - guard let self = self else { return } + guard let self = self else { return } let joinRuleEvent = state?.stateEvents(with: .roomJoinRules)?.last let allowContent: [[String: String]] = joinRuleEvent?.wireContent[kMXJoinRulesContentKeyAllow] as? [[String: String]] ?? [] @@ -74,19 +74,20 @@ class MatrixItemChooserRoomRestrictedAllowedParentsDataSource: MatrixItemChooser sections.append(MatrixListItemSectionData( title: VectorL10n.roomAccessSpaceChooserOtherSpacesSection, infoText: VectorL10n.roomAccessSpaceChooserOtherSpacesSectionInfo(room.displayName ?? ""), - items: unknownParents.compactMap({ roomId in + items: unknownParents.compactMap { roomId in MatrixListItemData( id: roomId, type: .space, avatar: AvatarInput(mxContentUri: roomId, matrixItemId: roomId, displayName: roomId), displayName: roomId, - detailText: nil) - }) + detailText: nil + ) + } )) } completion(Result(catching: { - return sections + sections })) } } diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomsDataSource.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomsDataSource.swift index 5906cdba1..348c4bb71 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomsDataSource.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomsDataSource.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserService.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserService.swift index 22a624413..28ed8a754 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserService.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation protocol MatrixItemChooserDataSource { func sections(with session: MXSession, completion: @escaping (Result<[MatrixListItemSectionData], Error>) -> Void) @@ -25,12 +25,11 @@ protocol MatrixItemChooserDataSource { protocol MatrixItemChooserProcessorProtocol { var loadingText: String? { get } var dataSource: MatrixItemChooserDataSource { get } - func computeSelection(withIds itemsIds:[String], completion: @escaping (Result) -> Void) - func isItemIncluded(_ item: (MatrixListItemData)) -> Bool + func computeSelection(withIds itemsIds: [String], completion: @escaping (Result) -> Void) + func isItemIncluded(_ item: MatrixListItemData) -> Bool } class MatrixItemChooserService: MatrixItemChooserServiceProtocol { - // MARK: - Properties // MARK: Private @@ -45,6 +44,7 @@ class MatrixItemChooserService: MatrixItemChooserServiceProtocol { sectionsSubject.send(filteredSections) } } + private var selectedItemIds: Set private let itemsProcessor: MatrixItemChooserProcessorProtocol @@ -52,14 +52,16 @@ class MatrixItemChooserService: MatrixItemChooserServiceProtocol { private(set) var sectionsSubject: CurrentValueSubject<[MatrixListItemSectionData], Never> private(set) var selectedItemIdsSubject: CurrentValueSubject, Never> - var searchText: String = "" { + var searchText = "" { didSet { refresh() } } + var loadingText: String? { itemsProcessor.loadingText } + var itemCount: Int { var itemCount = 0 for section in sections { @@ -72,10 +74,10 @@ class MatrixItemChooserService: MatrixItemChooserServiceProtocol { init(session: MXSession, selectedItemIds: [String], itemsProcessor: MatrixItemChooserProcessorProtocol) { self.session = session - self.sectionsSubject = CurrentValueSubject(self.sections) + sectionsSubject = CurrentValueSubject(sections) self.selectedItemIds = Set(selectedItemIds) - self.selectedItemIdsSubject = CurrentValueSubject(self.selectedItemIds) + selectedItemIdsSubject = CurrentValueSubject(self.selectedItemIds) self.itemsProcessor = itemsProcessor itemsProcessor.dataSource.sections(with: session) { [weak self] result in @@ -115,7 +117,7 @@ class MatrixItemChooserService: MatrixItemChooserServiceProtocol { } func refresh() { - self.processingQueue.async { [weak self] in + processingQueue.async { [weak self] in guard let self = self else { return } let filteredSections = self.filter(sections: self.sections) @@ -132,12 +134,12 @@ class MatrixItemChooserService: MatrixItemChooserServiceProtocol { newSelection.insert(item.id) } } - self.selectedItemIds = newSelection + selectedItemIds = newSelection selectedItemIdsSubject.send(selectedItemIds) } func deselectAllItems() { - self.selectedItemIds = Set() + selectedItemIds = Set() selectedItemIdsSubject.send(selectedItemIds) } @@ -153,7 +155,7 @@ class MatrixItemChooserService: MatrixItemChooserServiceProtocol { itemsProcessor.isItemIncluded($0) } } else { - let lowercasedSearchText = self.searchText.lowercased() + let lowercasedSearchText = searchText.lowercased() items = section.items.filter { itemsProcessor.isItemIncluded($0) && ($0.id.lowercased().contains(lowercasedSearchText) || ($0.displayName ?? "").lowercased().contains(lowercasedSearchText)) } diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserUsersDataSource.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserUsersDataSource.swift index 3194c37d7..184005866 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserUsersDataSource.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserUsersDataSource.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixListItemData+Riot.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixListItemData+Riot.swift index 5e8cc6119..292ad2ee0 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixListItemData+Riot.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixListItemData+Riot.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation extension MatrixListItemData { - init(mxUser: MXUser) { self.init(id: mxUser.userId, type: .user, avatar: mxUser.avatarData, displayName: mxUser.displayname, detailText: mxUser.userId) } @@ -54,5 +53,4 @@ extension MatrixListItemData { } self.init(id: mxRoom.roomId, type: type, avatar: mxRoom.avatarData, displayName: mxRoom.summary.displayname, detailText: detailText) } - } diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/Mock/MockMatrixItemChooserService.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/Mock/MockMatrixItemChooserService.swift index b08ede2fe..e1426bb6f 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/Mock/MockMatrixItemChooserService.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/Mock/MockMatrixItemChooserService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,11 +14,10 @@ // limitations under the License. // -import Foundation import Combine +import Foundation class MockMatrixItemChooserService: MatrixItemChooserServiceProtocol { - static let mockSections = [ MatrixListItemSectionData(title: "Section 1", infoText: "This is the first section with a very long description in order to check multi line description", items: [ MatrixListItemData(id: "!aaabaa:matrix.org", type: .room, avatar: MockAvatarInput.example, displayName: "Item #1 section #1", detailText: "Descripton of this room"), @@ -33,11 +32,12 @@ class MockMatrixItemChooserService: MatrixItemChooserServiceProtocol { ] var sectionsSubject: CurrentValueSubject<[MatrixListItemSectionData], Never> var selectedItemIdsSubject: CurrentValueSubject, Never> - var searchText: String = "" + var searchText = "" var selectedItemIds: Set = Set() var loadingText: String? { nil } + var itemCount: Int { var itemCount = 0 for section in sectionsSubject.value { @@ -82,9 +82,7 @@ class MockMatrixItemChooserService: MatrixItemChooserServiceProtocol { completion(Result.success(())) } - func refresh() { - - } + func refresh() { } func selectAllItems() { var newSelection: Set = Set() @@ -93,12 +91,12 @@ class MockMatrixItemChooserService: MatrixItemChooserServiceProtocol { newSelection.insert(item.id) } } - self.selectedItemIds = newSelection + selectedItemIds = newSelection selectedItemIdsSubject.send(selectedItemIds) } func deselectAllItems() { - self.selectedItemIds = Set() + selectedItemIds = Set() selectedItemIdsSubject.send(selectedItemIds) } } diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Test/UI/MatrixItemChooserUITests.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Test/UI/MatrixItemChooserUITests.swift index edcd683a3..52bccd9aa 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Test/UI/MatrixItemChooserUITests.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Test/UI/MatrixItemChooserUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class MatrixItemChooserUITests: MockScreenTestCase { func testEmptyScreen() { @@ -42,5 +42,4 @@ class MatrixItemChooserUITests: MockScreenTestCase { XCTAssertEqual(app.staticTexts["messageText"].label, VectorL10n.spacesCreationAddRoomsMessage) XCTAssertEqual(app.staticTexts["emptyListMessage"].exists, false) } - } diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Test/Unit/MatrixItemChooserViewModelTests.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Test/Unit/MatrixItemChooserViewModelTests.swift index 604674ce4..626707a1a 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Test/Unit/MatrixItemChooserViewModelTests.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Test/Unit/MatrixItemChooserViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooser.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooser.swift index eeb7743d3..6b7236788 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooser.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooser.swift @@ -19,12 +19,11 @@ import SwiftUI struct MatrixItemChooser: View { - // MARK: Properties @ObservedObject var viewModel: MatrixItemChooserViewModel.Context let listBottomPadding: CGFloat? - @State var searchText: String = "" + @State var searchText = "" // MARK: Private @@ -113,7 +112,7 @@ struct MatrixItemChooser: View { } Spacer().frame(height: spacerHeight) SearchBar(placeholder: VectorL10n.searchDefaultPlaceholder, text: $searchText) - .onChange(of: searchText) { value in + .onChange(of: searchText) { _ in viewModel.send(viewAction: .searchTextChanged(searchText)) } if let selectionHeader = viewModel.viewState.selectionHeader, searchText.isEmpty { @@ -124,7 +123,7 @@ struct MatrixItemChooser: View { } private func itemSelectionHeader(with selectionHeader: MatrixItemChooserSelectionHeader) -> some View { - VStack(alignment:.leading) { + VStack(alignment: .leading) { HStack { Text(selectionHeader.title) .font(theme.fonts.calloutSB) @@ -151,7 +150,6 @@ struct MatrixItemChooser: View { // MARK: - Previews struct MatrixItemChooser_Previews: PreviewProvider { - static let stateRenderer = MockMatrixItemChooserScreenState.stateRenderer static var previews: some View { stateRenderer.screenGroup(addNavigation: false) diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooserListRow.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooserListRow.swift index f5d6db9c8..1d67f2a72 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooserListRow.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooserListRow.swift @@ -17,7 +17,6 @@ import SwiftUI struct MatrixItemChooserListRow: View { - // MARK: - Properties // MARK: Private @@ -34,7 +33,7 @@ struct MatrixItemChooserListRow: View { @ViewBuilder var body: some View { - HStack{ + HStack { if type == .space { SpaceAvatarImage(avatarData: avatar, size: .small) } else { diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooserSectionHeader.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooserSectionHeader.swift index 632403587..a8a75c96a 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooserSectionHeader.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooserSectionHeader.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct MatrixItemChooserSectionHeader: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/Coordinator/RoomAncestorSelectorViewProvider.swift b/RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/Coordinator/RoomAncestorSelectorViewProvider.swift index 070a31967..1ec2d21f3 100644 --- a/RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/Coordinator/RoomAncestorSelectorViewProvider.swift +++ b/RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/Coordinator/RoomAncestorSelectorViewProvider.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI class RoomAncestorSelectorViewProvider: MatrixItemChooserCoordinatorViewProvider { - private let navTitle: String? init(navTitle: String?) { @@ -25,6 +24,6 @@ class RoomAncestorSelectorViewProvider: MatrixItemChooserCoordinatorViewProvider } func view(with viewModel: MatrixItemChooserViewModelType.Context) -> AnyView { - return AnyView(RoomAncestorSelector(viewModel: viewModel, navTitle: navTitle)) + AnyView(RoomAncestorSelector(viewModel: viewModel, navTitle: navTitle)) } } diff --git a/RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/Service/MatrixSDK/RoomAncestorSelectorItemsProcessor.swift b/RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/Service/MatrixSDK/RoomAncestorSelectorItemsProcessor.swift index 0c8f8884c..7b28dc2b2 100644 --- a/RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/Service/MatrixSDK/RoomAncestorSelectorItemsProcessor.swift +++ b/RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/Service/MatrixSDK/RoomAncestorSelectorItemsProcessor.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation class RoomAncestorSelectorItemsProcessor: MatrixItemChooserProcessorProtocol { - // MARK: Private private let roomId: String @@ -26,7 +25,7 @@ class RoomAncestorSelectorItemsProcessor: MatrixItemChooserProcessorProtocol { init(roomId: String) { self.roomId = roomId - self.dataSource = MatrixItemChooserRoomAncestorsDataSource(roomId: roomId) + dataSource = MatrixItemChooserRoomAncestorsDataSource(roomId: roomId) } // MARK: MatrixItemChooserSelectionProcessorProtocol @@ -41,7 +40,7 @@ class RoomAncestorSelectorItemsProcessor: MatrixItemChooserProcessorProtocol { completion(Result.success(())) } - func isItemIncluded(_ item: (MatrixListItemData)) -> Bool { - return true + func isItemIncluded(_ item: MatrixListItemData) -> Bool { + true } } diff --git a/RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/View/RoomAncestorSelector.swift b/RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/View/RoomAncestorSelector.swift index 91ac64a40..3d1c2557a 100644 --- a/RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/View/RoomAncestorSelector.swift +++ b/RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/View/RoomAncestorSelector.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct RoomAncestorSelector: View { - // MARK: Properties @ObservedObject var viewModel: MatrixItemChooserViewModel.Context @@ -46,5 +45,4 @@ struct RoomAncestorSelector: View { } } } - } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinator.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinator.swift index 592fe50bd..ad7e56d2a 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinator.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinator.swift @@ -22,7 +22,6 @@ import UIKit @objcMembers final class SpaceCreationCoordinator: Coordinator { - // MARK: - Properties // MARK: Private @@ -30,7 +29,7 @@ final class SpaceCreationCoordinator: Coordinator { private let parameters: SpaceCreationCoordinatorParameters private var navigationRouter: NavigationRouterType { - return self.parameters.navigationRouter + parameters.navigationRouter } private let spaceVisibilityMenuParameters: SpaceCreationMenuCoordinatorParameters @@ -57,7 +56,7 @@ final class SpaceCreationCoordinator: Coordinator { } self.parameters = parameters - self.spaceVisibilityMenuParameters = SpaceCreationMenuCoordinatorParameters( + spaceVisibilityMenuParameters = SpaceCreationMenuCoordinatorParameters( session: parameters.session, creationParams: parameters.creationParameters, navTitle: VectorL10n.spacesCreateSpaceTitle, @@ -70,7 +69,7 @@ final class SpaceCreationCoordinator: Coordinator { ] ) - self.spaceSharingTypeMenuParameters = SpaceCreationMenuCoordinatorParameters( + spaceSharingTypeMenuParameters = SpaceCreationMenuCoordinatorParameters( session: parameters.session, creationParams: parameters.creationParameters, navTitle: nil, @@ -91,26 +90,26 @@ final class SpaceCreationCoordinator: Coordinator { Analytics.shared.trackScreen(.createSpace) - let rootCoordinator = self.createMenuCoordinator(with: spaceVisibilityMenuParameters) + let rootCoordinator = createMenuCoordinator(with: spaceVisibilityMenuParameters) rootCoordinator.start() - self.add(childCoordinator: rootCoordinator) + add(childCoordinator: rootCoordinator) - self.toPresentable().isModalInPresentation = true + toPresentable().isModalInPresentation = true - if self.navigationRouter.modules.isEmpty == false { - self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in + if navigationRouter.modules.isEmpty == false { + navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in self?.remove(childCoordinator: rootCoordinator) }) } else { - self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in + navigationRouter.setRootModule(rootCoordinator) { [weak self] in self?.remove(childCoordinator: rootCoordinator) } } } func toPresentable() -> UIViewController { - return self.navigationRouter.toPresentable() + navigationRouter.toPresentable() } // MARK: - Private @@ -118,7 +117,7 @@ final class SpaceCreationCoordinator: Coordinator { func pushScreen(with coordinator: Coordinator & Presentable) { add(childCoordinator: coordinator) - self.navigationRouter.push(coordinator, animated: true, popCompletion: { [weak self] in + navigationRouter.push(coordinator, animated: true, popCompletion: { [weak self] in self?.remove(childCoordinator: coordinator) }) @@ -126,7 +125,7 @@ final class SpaceCreationCoordinator: Coordinator { } private func createMenuCoordinator(with parameters: SpaceCreationMenuCoordinatorParameters) -> SpaceCreationMenuCoordinator { - let coordinator: SpaceCreationMenuCoordinator = SpaceCreationMenuCoordinator(parameters: parameters) + let coordinator = SpaceCreationMenuCoordinator(parameters: parameters) coordinator.callback = { [weak self] result in MXLog.debug("[SpaceCreationCoordinator] SpaceCreationMenuCoordinator did complete with result \(result).") @@ -217,7 +216,8 @@ final class SpaceCreationCoordinator: Coordinator { detail: VectorL10n.spacesCreationInviteByUsernameMessage, selectedItemsIds: parameters.creationParameters.userIdInvites, viewProvider: SpaceCreationMatrixItemChooserViewProvider(), - itemsProcessor: SpaceCreationInviteUsersItemsProcessor(creationParams: parameters.creationParameters)) + itemsProcessor: SpaceCreationInviteUsersItemsProcessor(creationParams: parameters.creationParameters) + ) let coordinator = MatrixItemChooserCoordinator(parameters: parameters) coordinator.completion = { [weak self] result in guard let self = self else { return } @@ -240,7 +240,8 @@ final class SpaceCreationCoordinator: Coordinator { detail: VectorL10n.spacesCreationAddRoomsMessage, selectedItemsIds: parameters.creationParameters.addedRoomIds ?? [], viewProvider: SpaceCreationMatrixItemChooserViewProvider(), - itemsProcessor: SpaceCreationAddRoomsItemsProcessor(creationParams: parameters.creationParameters)) + itemsProcessor: SpaceCreationAddRoomsItemsProcessor(creationParams: parameters.creationParameters) + ) let coordinator = MatrixItemChooserCoordinator(parameters: parameters) coordinator.completion = { [weak self] result in guard let self = self else { return } @@ -275,13 +276,13 @@ final class SpaceCreationCoordinator: Coordinator { UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) let alert = UIAlertController(title: VectorL10n.spacesCreationCancelTitle, message: VectorL10n.spacesCreationCancelMessage, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: VectorL10n.stop, style: .destructive, handler: { action in + alert.addAction(UIAlertAction(title: VectorL10n.stop, style: .destructive, handler: { _ in self.callback?(.cancel) })) alert.addAction(UIAlertAction(title: VectorL10n.continue, style: .cancel, handler: nil)) navigationRouter.present(alert, animated: true) } else { - self.callback?(.cancel) + callback?(.cancel) } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinatorAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinatorAction.swift index 879d8e0f8..fee96634a 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinatorAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinatorAction.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinatorParameters.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinatorParameters.swift index f965b81ab..a78eebcfb 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinatorParameters.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinatorParameters.swift @@ -22,7 +22,6 @@ import Foundation /// SpaceCreationCoordinator input parameters struct SpaceCreationCoordinatorParameters { - /// The Matrix session let session: MXSession @@ -30,7 +29,7 @@ struct SpaceCreationCoordinatorParameters { let parentSpaceId: String? /// Parameters needed to create the new space - let creationParameters: SpaceCreationParameters = SpaceCreationParameters() + let creationParameters = SpaceCreationParameters() /// The navigation router that manage physical navigation let navigationRouter: NavigationRouterType diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Coordinator/SpaceCreationEmailInvitesCoordinator.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Coordinator/SpaceCreationEmailInvitesCoordinator.swift index 986f03b35..28792056f 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Coordinator/SpaceCreationEmailInvitesCoordinator.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Coordinator/SpaceCreationEmailInvitesCoordinator.swift @@ -17,11 +17,10 @@ */ import Foundation -import UIKit import SwiftUI +import UIKit final class SpaceCreationEmailInvitesCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -75,7 +74,7 @@ final class SpaceCreationEmailInvitesCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.spaceCreationEmailInvitesHostingController + spaceCreationEmailInvitesHostingController } // MARK: - Identity service @@ -90,34 +89,34 @@ final class SpaceCreationEmailInvitesCoordinator: Coordinator, Presentable { let presenter = ServiceTermsModalCoordinatorBridgePresenter(session: parameters.session, baseUrl: baseUrl, serviceType: MXServiceTypeIdentityService, accessToken: accessToken) presenter.delegate = self - presenter.present(from: self.toPresentable(), animated: true) + presenter.present(from: toPresentable(), animated: true) serviceTermsModalCoordinatorBridgePresenter = presenter } private func showIdentityServiceFailure(_ error: Error?) { let alertController = UIAlertController(title: VectorL10n.findYourContactsIdentityServiceError, message: nil, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: VectorL10n.ok, style: .default, handler: nil)) - self.toPresentable().present(alertController, animated: true, completion: nil); + toPresentable().present(alertController, animated: true, completion: nil) } } extension SpaceCreationEmailInvitesCoordinator: ServiceTermsModalCoordinatorBridgePresenterDelegate { func serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter) { coordinatorBridgePresenter.dismiss(animated: true) { - self.serviceTermsModalCoordinatorBridgePresenter = nil; + self.serviceTermsModalCoordinatorBridgePresenter = nil self.callback?(.done) } } func serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter, session: MXSession) { coordinatorBridgePresenter.dismiss(animated: true) { - self.serviceTermsModalCoordinatorBridgePresenter = nil; + self.serviceTermsModalCoordinatorBridgePresenter = nil } } func serviceTermsModalCoordinatorBridgePresenterDelegateDidClose(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter) { coordinatorBridgePresenter.dismiss(animated: true) { - self.serviceTermsModalCoordinatorBridgePresenter = nil; + self.serviceTermsModalCoordinatorBridgePresenter = nil } } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Coordinator/SpaceCreationEmailInvitesCoordinatorParameters.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Coordinator/SpaceCreationEmailInvitesCoordinatorParameters.swift index af2ac696c..3b2d32f78 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Coordinator/SpaceCreationEmailInvitesCoordinatorParameters.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Coordinator/SpaceCreationEmailInvitesCoordinatorParameters.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationEmailInvites SpaceCreationEmailInvites -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesCoordinatorAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesCoordinatorAction.swift index cec48fc61..5eb47645f 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesCoordinatorAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesCoordinatorAction.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesStateAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesStateAction.swift index ead48ebf2..b266fbfb7 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesStateAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesStateAction.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationEmailInvites SpaceCreationEmailInvites -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesViewAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesViewAction.swift index 0f08acea8..b2fbfc34f 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesViewAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesViewAction.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationEmailInvites SpaceCreationEmailInvites -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesViewModelBindings.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesViewModelBindings.swift index 531689fa1..39b560c62 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesViewModelBindings.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesViewModelBindings.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesViewModelResult.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesViewModelResult.swift index d4f55e4a3..06bf52215 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesViewModelResult.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesViewModelResult.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationEmailInvites SpaceCreationEmailInvites -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesViewState.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesViewState.swift index e3028134f..ee7d48e08 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesViewState.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesViewState.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationEmailInvites SpaceCreationEmailInvites -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/MatrixSDK/SpaceCreationEmailInvitesService.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/MatrixSDK/SpaceCreationEmailInvitesService.swift index e011cbc59..e49d6030a 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/MatrixSDK/SpaceCreationEmailInvitesService.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/MatrixSDK/SpaceCreationEmailInvitesService.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationEmailInvites SpaceCreationEmailInvites -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,11 +16,10 @@ // limitations under the License. // -import Foundation import Combine +import Foundation class SpaceCreationEmailInvitesService: SpaceCreationEmailInvitesServiceProtocol { - private let session: MXSession private(set) var isLoadingSubject: CurrentValueSubject @@ -37,12 +36,12 @@ class SpaceCreationEmailInvitesService: SpaceCreationEmailInvitesServiceProtocol } func validate(_ emailAddresses: [String]) -> [Bool] { - return emailAddresses.map { $0.isEmpty || MXTools.isEmailAddress($0) } + emailAddresses.map { $0.isEmpty || MXTools.isEmailAddress($0) } } func prepareIdentityService(prepared: ((String?, String?) -> Void)?, failure: ((Error?) -> Void)?) { isLoadingSubject.send(true) - session.prepareIdentityServiceForTerms(withDefault: RiotSettings.shared.identityServerUrlString) { [weak self] session, baseURL, accessToken in + session.prepareIdentityServiceForTerms(withDefault: RiotSettings.shared.identityServerUrlString) { [weak self] _, baseURL, accessToken in self?.isLoadingSubject.send(false) prepared?(baseURL, accessToken) } failure: { [weak self] error in diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/Mock/MockSpaceCreationEmailInvitesScreenState.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/Mock/MockSpaceCreationEmailInvitesScreenState.swift index 09a3d4b82..0b70a8746 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/Mock/MockSpaceCreationEmailInvitesScreenState.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/Mock/MockSpaceCreationEmailInvitesScreenState.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationEmailInvites SpaceCreationEmailInvites -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,7 +41,7 @@ enum MockSpaceCreationEmailInvitesScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let creationParams = SpaceCreationParameters() let service: MockSpaceCreationEmailInvitesService switch self { diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/Mock/MockSpaceCreationEmailInvitesService.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/Mock/MockSpaceCreationEmailInvitesService.swift index a34a9b801..5c308f044 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/Mock/MockSpaceCreationEmailInvitesService.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/Mock/MockSpaceCreationEmailInvitesService.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationEmailInvites SpaceCreationEmailInvites -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation class MockSpaceCreationEmailInvitesService: SpaceCreationEmailInvitesServiceProtocol { var isLoadingSubject: CurrentValueSubject @@ -25,16 +25,16 @@ class MockSpaceCreationEmailInvitesService: SpaceCreationEmailInvitesServiceProt private let defaultValidation: Bool var isIdentityServiceReady: Bool { - return true + true } init(defaultValidation: Bool, isLoading: Bool) { self.defaultValidation = defaultValidation - self.isLoadingSubject = CurrentValueSubject(isLoading) + isLoadingSubject = CurrentValueSubject(isLoading) } func validate(_ emailAddresses: [String]) -> [Bool] { - return emailAddresses.map { _ in defaultValidation } + emailAddresses.map { _ in defaultValidation } } func prepareIdentityService(prepared: ((String?, String?) -> Void)?, failure: ((Error?) -> Void)?) { diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/SpaceCreationEmailInvitesServiceProtocol.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/SpaceCreationEmailInvitesServiceProtocol.swift index c92a2cd8d..cae561374 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/SpaceCreationEmailInvitesServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/SpaceCreationEmailInvitesServiceProtocol.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationEmailInvites SpaceCreationEmailInvites -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation protocol SpaceCreationEmailInvitesServiceProtocol { var isIdentityServiceReady: Bool { get } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Test/UI/SpaceCreationEmailInvitesUITests.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Test/UI/SpaceCreationEmailInvitesUITests.swift index 897230312..9397206b4 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Test/UI/SpaceCreationEmailInvitesUITests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Test/UI/SpaceCreationEmailInvitesUITests.swift @@ -16,8 +16,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class SpaceCreationEmailInvitesUITests: MockScreenTestCase { func testDefaultEmailValues() { diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Test/Unit/SpaceCreationEmailInvitesViewModelTests.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Test/Unit/SpaceCreationEmailInvitesViewModelTests.swift index bbea5e3e2..b2477d145 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Test/Unit/SpaceCreationEmailInvitesViewModelTests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Test/Unit/SpaceCreationEmailInvitesViewModelTests.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationEmailInvites SpaceCreationEmailInvites -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,8 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/View/SpaceCreationEmailInvites.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/View/SpaceCreationEmailInvites.swift index 80f9abe87..ddaca92f1 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/View/SpaceCreationEmailInvites.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/View/SpaceCreationEmailInvites.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationEmailInvites SpaceCreationEmailInvites -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import SwiftUI struct SpaceCreationEmailInvites: View { - // MARK: - Properties @ObservedObject var viewModel: SpaceCreationEmailInvitesViewModel.Context @@ -85,7 +84,7 @@ struct SpaceCreationEmailInvites: View { private var formView: some View { VStack { VStack(spacing: 20) { - ForEach(viewModel.emailInvites.indices) { index in + ForEach(viewModel.emailInvites.indices, id: \.self) { index in RoundedBorderTextField(title: VectorL10n.spacesCreationEmailInvitesEmailTitle, placeHolder: VectorL10n.spacesCreationEmailInvitesEmailTitle, text: $viewModel.emailInvites[index], footerText: viewModel.viewState.emailAddressesValid[index] ? nil : VectorL10n.authInvalidEmail, isError: !viewModel.viewState.emailAddressesValid[index], configuration: UIKitTextInputConfiguration(keyboardType: .emailAddress, returnKeyType: index < viewModel.emailInvites.endIndex - 1 ? .next : .done, autocapitalizationType: .none, autocorrectionType: .no)) .accessibility(identifier: "emailTextField") } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModel.swift index 55a2c6750..239dadb6f 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModel.swift @@ -16,14 +16,13 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias SpaceCreationEmailInvitesViewModelType = StateStoreViewModel + SpaceCreationEmailInvitesStateAction, + SpaceCreationEmailInvitesViewAction> class SpaceCreationEmailInvitesViewModel: SpaceCreationEmailInvitesViewModelType, SpaceCreationEmailInvitesViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -86,14 +85,14 @@ class SpaceCreationEmailInvitesViewModel: SpaceCreationEmailInvitesViewModelType } private func done() { - self.creationParameters.emailInvites = self.context.emailInvites - self.creationParameters.inviteType = .email - let emailAddressesValidity = service.validate(self.context.emailInvites) + creationParameters.emailInvites = context.emailInvites + creationParameters.inviteType = .email + let emailAddressesValidity = service.validate(context.emailInvites) dispatch(action: .updateEmailValidity(emailAddressesValidity)) - if self.context.emailInvites.reduce(true, { $0 && $1.isEmpty }) { + if context.emailInvites.reduce(true, { $0 && $1.isEmpty }) { completion?(.done) - } else if emailAddressesValidity.reduce(true, { $0 && $1}) { + } else if emailAddressesValidity.reduce(true, { $0 && $1 }) { if service.isIdentityServiceReady { completion?(.done) } else { diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModelProtocol.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModelProtocol.swift index 88c154805..72b28d33b 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModelProtocol.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationEmailInvites SpaceCreationEmailInvites -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import Foundation protocol SpaceCreationEmailInvitesViewModelProtocol { - var completion: ((SpaceCreationEmailInvitesViewModelResult) -> Void)? { get set } var context: SpaceCreationEmailInvitesViewModelType.Context { get } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/Coordinator/SpaceCreationMatrixItemChooserViewProvider.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/Coordinator/SpaceCreationMatrixItemChooserViewProvider.swift index a4e80561f..f22a4a978 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/Coordinator/SpaceCreationMatrixItemChooserViewProvider.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/Coordinator/SpaceCreationMatrixItemChooserViewProvider.swift @@ -18,6 +18,6 @@ import SwiftUI class SpaceCreationMatrixItemChooserViewProvider: MatrixItemChooserCoordinatorViewProvider { func view(with viewModel: MatrixItemChooserViewModelType.Context) -> AnyView { - return AnyView(SpaceCreationMatrixItemChooser(viewModel: viewModel)) + AnyView(SpaceCreationMatrixItemChooser(viewModel: viewModel)) } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/Service/MatrixSDK/SpaceCreationAddRoomsItemsProcessor.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/Service/MatrixSDK/SpaceCreationAddRoomsItemsProcessor.swift index 4638d585e..03c32f7f7 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/Service/MatrixSDK/SpaceCreationAddRoomsItemsProcessor.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/Service/MatrixSDK/SpaceCreationAddRoomsItemsProcessor.swift @@ -17,7 +17,6 @@ import Foundation class SpaceCreationAddRoomsItemsProcessor: MatrixItemChooserProcessorProtocol { - // MARK: Private private let creationParams: SpaceCreationParameters @@ -41,8 +40,7 @@ class SpaceCreationAddRoomsItemsProcessor: MatrixItemChooserProcessorProtocol { completion(.success(())) } - func isItemIncluded(_ item: (MatrixListItemData)) -> Bool { - return true + func isItemIncluded(_ item: MatrixListItemData) -> Bool { + true } - } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/Service/MatrixSDK/SpaceCreationInviteUsersItemsProcessor.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/Service/MatrixSDK/SpaceCreationInviteUsersItemsProcessor.swift index 327f6589b..5fb15d88a 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/Service/MatrixSDK/SpaceCreationInviteUsersItemsProcessor.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/Service/MatrixSDK/SpaceCreationInviteUsersItemsProcessor.swift @@ -17,7 +17,6 @@ import Foundation class SpaceCreationInviteUsersItemsProcessor: MatrixItemChooserProcessorProtocol { - // MARK: Private private let creationParams: SpaceCreationParameters @@ -42,8 +41,7 @@ class SpaceCreationInviteUsersItemsProcessor: MatrixItemChooserProcessorProtocol completion(.success(())) } - func isItemIncluded(_ item: (MatrixListItemData)) -> Bool { - return true + func isItemIncluded(_ item: MatrixListItemData) -> Bool { + true } - } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/View/SpaceCreationMatrixItemChooser.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/View/SpaceCreationMatrixItemChooser.swift index 856b5c6c1..f1d6f5453 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/View/SpaceCreationMatrixItemChooser.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/View/SpaceCreationMatrixItemChooser.swift @@ -17,7 +17,6 @@ import SwiftUI struct SpaceCreationMatrixItemChooser: View { - // MARK: Properties @ObservedObject var viewModel: MatrixItemChooserViewModel.Context @@ -52,7 +51,6 @@ struct SpaceCreationMatrixItemChooser: View { } } - @ViewBuilder private var footerView: some View { ThemableButton(icon: nil, title: viewModel.viewState.selectedItemIds.isEmpty ? VectorL10n.skip : VectorL10n.next) { diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Coordinator/SpaceCreationMenuCoordinator.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Coordinator/SpaceCreationMenuCoordinator.swift index 286f9b2e2..8d590936d 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Coordinator/SpaceCreationMenuCoordinator.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Coordinator/SpaceCreationMenuCoordinator.swift @@ -17,11 +17,10 @@ */ import Foundation -import UIKit import SwiftUI +import UIKit final class SpaceCreationMenuCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -63,12 +62,11 @@ final class SpaceCreationMenuCoordinator: Coordinator, Presentable { self.callback?(.cancel) case .back: self.callback?(.back) - break } } } func toPresentable() -> UIViewController { - return self.spaceCreationMenuHostingController + spaceCreationMenuHostingController } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Coordinator/SpaceCreationMenuCoordinatorParamaters.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Coordinator/SpaceCreationMenuCoordinatorParamaters.swift index 099c12b68..51c8108b3 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Coordinator/SpaceCreationMenuCoordinatorParamaters.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Coordinator/SpaceCreationMenuCoordinatorParamaters.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuCoordinatorAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuCoordinatorAction.swift index c3715ee52..148623a05 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuCoordinatorAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuCoordinatorAction.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuRoom.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuRoom.swift index 27c95e48c..1d16fa0a2 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuRoom.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuRoom.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,4 +38,4 @@ struct SpaceCreationMenuRoomOption { let detail: String } -extension SpaceCreationMenuRoomOption: Identifiable, Equatable {} +extension SpaceCreationMenuRoomOption: Identifiable, Equatable { } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuStateAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuStateAction.swift index 3109b348b..15c7a4ffc 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuStateAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuStateAction.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,5 +19,4 @@ import Foundation /// Actions to be performed on the `ViewModel` State -enum SpaceCreationMenuStateAction { -} +enum SpaceCreationMenuStateAction { } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuViewAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuViewAction.swift index 7d0afb05e..35e44e4a1 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuViewAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuViewAction.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuViewModelAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuViewModelAction.swift index 079ddab25..f8e415d34 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuViewModelAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuViewModelAction.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuViewState.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuViewState.swift index d25f2bf1a..fd71eaf89 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuViewState.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuViewState.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationParameters.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationParameters.swift index a9c2036f4..a07e840de 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationParameters.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationParameters.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,26 +28,31 @@ class SpaceCreationParameters { isModified = true } } + var topic: String? { didSet { isModified = true } } + var address: String? { didSet { isModified = true } } + var userDefinedAddress: String? { didSet { isModified = true } } - var isPublic: Bool = false { + + var isPublic = false { didSet { isModified = true } } + var showAddress: Bool { isPublic } @@ -57,7 +62,8 @@ class SpaceCreationParameters { isModified = true } } - var isShared: Bool = false { + + var isShared = false { didSet { isModified = true } @@ -84,22 +90,26 @@ class SpaceCreationParameters { isModified = true } } + var userDefinedEmailInvites: [String] { - return emailInvites.filter { address in - return !address.isEmpty + emailInvites.filter { address in + !address.isEmpty } } + var userIdInvites: [String] = [] { didSet { isModified = true } } + var inviteType: SpaceCreationInviteType = .email { didSet { isModified = true } } - var isModified: Bool = false + + var isModified = false } struct SpaceCreationNewRoom: Equatable { @@ -107,6 +117,6 @@ struct SpaceCreationNewRoom: Equatable { var defaultName: String static func == (lhs: Self, rhs: Self) -> Bool { - return lhs.defaultName == rhs.defaultName && lhs.name == rhs.name + lhs.defaultName == rhs.defaultName && lhs.name == rhs.name } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Test/UI/SpaceCreationMenuUITests.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Test/UI/SpaceCreationMenuUITests.swift index 251fb5429..d6c5d47df 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Test/UI/SpaceCreationMenuUITests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Test/UI/SpaceCreationMenuUITests.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,14 +16,14 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class SpaceCreationMenuUITests: MockScreenTestCase { func testSpaceCreationMenuOptions() { app.goToScreenWithIdentifier(MockSpaceCreationMenuScreenState.options.title) - let optionButtonCount = app.buttons.matching(identifier:"optionButton").count + let optionButtonCount = app.buttons.matching(identifier: "optionButton").count XCTAssertEqual(optionButtonCount, 2) let titleText = app.staticTexts["titleText"] diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Test/Unit/SpaceCreationMenuViewModelTests.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Test/Unit/SpaceCreationMenuViewModelTests.swift index 72c694556..9c3561724 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Test/Unit/SpaceCreationMenuViewModelTests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Test/Unit/SpaceCreationMenuViewModelTests.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,14 +16,13 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI class SpaceCreationMenuViewModelTests: XCTestCase { - private enum Constants { - } + private enum Constants { } let navTitle = VectorL10n.spacesCreateSpaceTitle var creationParams = SpaceCreationParameters() diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/View/SpaceCreationMenu.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/View/SpaceCreationMenu.swift index fc1c80593..aac7cb983 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/View/SpaceCreationMenu.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/View/SpaceCreationMenu.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import SwiftUI struct SpaceCreationMenu: View { - // MARK: - Properties @ObservedObject var viewModel: SpaceCreationMenuViewModelType.Context @@ -100,7 +99,6 @@ struct SpaceCreationMenu: View { // MARK: - Previews struct SpaceCreationMenu_Previews: PreviewProvider { - static let stateRenderer = MockSpaceCreationMenuScreenState.stateRenderer static var previews: some View { diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModel.swift index 62b48d3a7..ffbf21dac 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModel.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,14 +16,13 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias SpaceCreationMenuViewModelType = StateStoreViewModel + SpaceCreationMenuStateAction, + SpaceCreationMenuViewAction> class SpaceCreationMenuViewModel: SpaceCreationMenuViewModelType, SpaceCreationMenuViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -43,7 +42,7 @@ class SpaceCreationMenuViewModel: SpaceCreationMenuViewModelType, SpaceCreationM } private static func defaultState(navTitle: String?, creationParams: SpaceCreationParameters, title: String, detail: String, options: [SpaceCreationMenuRoomOption]) -> SpaceCreationMenuViewState { - var navigationTitle: String = "" + var navigationTitle = "" if let navTitle = navTitle { navigationTitle = navTitle } else { @@ -60,13 +59,13 @@ class SpaceCreationMenuViewModel: SpaceCreationMenuViewModelType, SpaceCreationM case .didSelectOption(let optionId): switch optionId { case .publicSpace: - self.creationParams.isPublic = true + creationParams.isPublic = true case .privateSpace: - self.creationParams.isPublic = false + creationParams.isPublic = false case .ownedPrivateSpace: - self.creationParams.isShared = false + creationParams.isShared = false case .sharedPrivateSpace: - self.creationParams.isShared = true + creationParams.isShared = true } didSelectOption(withId: optionId) diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModelProtocol.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModelProtocol.swift index f3660dd08..29fd9b10a 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModelProtocol.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Coordinator/SpaceCreationPostProcessCoordinator.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Coordinator/SpaceCreationPostProcessCoordinator.swift index f5c46f7e6..a1838458d 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Coordinator/SpaceCreationPostProcessCoordinator.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Coordinator/SpaceCreationPostProcessCoordinator.swift @@ -17,11 +17,10 @@ */ import Foundation -import UIKit import SwiftUI +import UIKit final class SpaceCreationPostProcessCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -50,6 +49,7 @@ final class SpaceCreationPostProcessCoordinator: Coordinator, Presentable { } // MARK: - Public + func start() { MXLog.debug("[SpaceCreationPostProcessCoordinator] did start.") spaceCreationPostProcessViewModel.completion = { [weak self] result in @@ -65,6 +65,6 @@ final class SpaceCreationPostProcessCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.spaceCreationPostProcessHostingController + spaceCreationPostProcessHostingController } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Coordinator/SpaceCreationPostProcessCoordinatorParameters.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Coordinator/SpaceCreationPostProcessCoordinatorParameters.swift index 68a99c879..b71b1f773 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Coordinator/SpaceCreationPostProcessCoordinatorParameters.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Coordinator/SpaceCreationPostProcessCoordinatorParameters.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationPostProcess SpaceCreationPostProcess -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessCoordinatorAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessCoordinatorAction.swift index d384c3a5d..5d27c5fa2 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessCoordinatorAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessCoordinatorAction.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessStateAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessStateAction.swift index 31003ed4e..880de2a4d 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessStateAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessStateAction.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationPostProcess SpaceCreationPostProcess -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessTask.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessTask.swift index 6c2d8baf1..c02e27f55 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessTask.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessTask.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,11 +38,12 @@ struct SpaceCreationPostProcessTask: Equatable { let title: String var state: SpaceCreationPostProcessTaskState var isFinished: Bool { - return state == .failure || state == .success + state == .failure || state == .success } + var subTasks: [SpaceCreationPostProcessTask] = [] static func == (lhs: SpaceCreationPostProcessTask, rhs: SpaceCreationPostProcessTask) -> Bool { - return lhs.type == rhs.type && lhs.title == rhs.title && lhs.state == rhs.state && lhs.subTasks == lhs.subTasks + lhs.type == rhs.type && lhs.title == rhs.title && lhs.state == rhs.state && lhs.subTasks == lhs.subTasks } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessViewAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessViewAction.swift index dc7397f46..ad1a80a15 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessViewAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessViewAction.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationPostProcess SpaceCreationPostProcess -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessViewModelResult.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessViewModelResult.swift index 38ee7179d..39221a3c1 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessViewModelResult.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessViewModelResult.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationPostProcess SpaceCreationPostProcess -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessViewState.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessViewState.swift index 311b2fcba..1fe80fff8 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessViewState.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Model/SpaceCreationPostProcessViewState.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationPostProcess SpaceCreationPostProcess -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/MatrixSDK/SpaceCreationPostProcessService.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/MatrixSDK/SpaceCreationPostProcessService.swift index 99a45c3cd..4fd4fe06d 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/MatrixSDK/SpaceCreationPostProcessService.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/MatrixSDK/SpaceCreationPostProcessService.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationPostProcess SpaceCreationPostProcess -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,12 +16,11 @@ // limitations under the License. // -import Foundation import Combine +import Foundation import MatrixSDK class SpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol { - // MARK: - Properties // MARK: Private @@ -39,19 +38,16 @@ class SpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol { createdSpaceId = createdSpace?.spaceId } } + private var createdRoomsByName: [String: MXRoom] = [:] private var currentSubTaskIndex = 0 private var processingQueue = DispatchQueue(label: "io.element.MXSpace.processingQueue", attributes: .concurrent) - private lazy var stateEventBuilder: MXRoomInitialStateEventBuilder = { - return MXRoomInitialStateEventBuilder() - }() + private lazy var stateEventBuilder = MXRoomInitialStateEventBuilder() - private lazy var mediaUploader: MXMediaLoader = { - return MXMediaManager.prepareUploader(withMatrixSession: session, initialRange: 0, andRange: 1.0) - }() + private lazy var mediaUploader: MXMediaLoader = MXMediaManager.prepareUploader(withMatrixSession: session, initialRange: 0, andRange: 1.0) // MARK: Public @@ -61,8 +57,9 @@ class SpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol { let alias = creationParams.userDefinedAddress.isEmptyOrNil ? creationParams.address : creationParams.userDefinedAddress return AvatarInput(mxContentUri: alias, matrixItemId: "", displayName: creationParams.name) } + var avatarImage: UIImage? { - return creationParams.userSelectedAvatar + creationParams.userSelectedAvatar } // MARK: - Setup @@ -71,18 +68,17 @@ class SpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol { self.session = session self.parentSpaceId = parentSpaceId self.creationParams = creationParams - self.tasks = Self.tasks(with: creationParams) - self.tasksSubject = CurrentValueSubject(tasks) + tasks = Self.tasks(with: creationParams) + tasksSubject = CurrentValueSubject(tasks) } - deinit { - } + deinit { } // MARK: - Public func run() { - self.isRetry = self.currentTaskIndex > 0 - self.currentTaskIndex = -1 + isRetry = currentTaskIndex > 0 + currentTaskIndex = -1 runNextTask() } @@ -106,13 +102,13 @@ class SpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol { tasks.append(SpaceCreationPostProcessTask(type: .addRooms, title: VectorL10n.spacesCreationPostProcessAddingRooms("\(addedRoomIds.count)"), state: .none, subTasks: subTasks)) } } else { - tasks.append(contentsOf: creationParams.newRooms.compactMap({ room in + tasks.append(contentsOf: creationParams.newRooms.compactMap { room in guard !room.name.isEmpty else { return nil } return SpaceCreationPostProcessTask(type: .createRoom(room.name), title: VectorL10n.spacesCreationPostProcessCreatingRoom(room.name), state: .none) - })) + }) } if creationParams.inviteType == .email { @@ -183,7 +179,7 @@ class SpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol { return } - parentSpace.addChild(roomId: createdSpaceId) { [weak self] response in + parentSpace.addChild(roomId: createdSpaceId) { [weak self] _ in guard let self = self else { return } self.updateCurrentTask(with: .success) @@ -194,45 +190,45 @@ class SpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol { } private func uploadAvatar(andUpdate task: SpaceCreationPostProcessTask) { - self.updateCurrentTask(with: .started) + updateCurrentTask(with: .started) - guard let avatar = creationParams.userSelectedAvatar, let spaceRoom = self.createdSpace?.room else { - self.updateCurrentTask(with: .success) - self.runNextTask() + guard let avatar = creationParams.userSelectedAvatar, let spaceRoom = createdSpace?.room else { + updateCurrentTask(with: .success) + runNextTask() return } let avatarUp = MXKTools.forceImageOrientationUp(avatar) mediaUploader.uploadData(avatarUp?.jpegData(compressionQuality: 0.5), filename: nil, mimeType: "image/jpeg", - success: { [weak self] (urlString) in - guard let self = self else { return } - guard let urlString = urlString else { return } - guard let url = URL(string: urlString) else { return } + success: { [weak self] urlString in + guard let self = self else { return } + guard let urlString = urlString else { return } + guard let url = URL(string: urlString) else { return } - self.setAvatar(ofRoom: spaceRoom, withURL: url, andUpdate: task) + self.setAvatar(ofRoom: spaceRoom, withURL: url, andUpdate: task) }, - failure: { [weak self] (error) in - guard let self = self else { return } + failure: { [weak self] _ in + guard let self = self else { return } - self.updateCurrentTask(with: .failure) - self.runNextTask() + self.updateCurrentTask(with: .failure) + self.runNextTask() }) } private func setAvatar(ofRoom room: MXRoom, withURL url: URL, andUpdate task: SpaceCreationPostProcessTask) { updateCurrentTask(with: .started) - room.setAvatar(url: url) { [weak self] (response) in + room.setAvatar(url: url) { [weak self] response in guard let self = self else { return } - self.updateCurrentTask(with: response.isSuccess ? .success: .failure) + self.updateCurrentTask(with: response.isSuccess ? .success : .failure) self.runNextTask() } } private func createRoom(withName roomName: String, andUpdate task: SpaceCreationPostProcessTask) { - guard let createdSpace = self.createdSpace else { + guard let createdSpace = createdSpace else { updateCurrentTask(with: .failure) runNextTask() return @@ -257,7 +253,7 @@ class SpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol { } private func addToSpace(room: MXRoom) { - guard let createdSpace = self.createdSpace else { + guard let createdSpace = createdSpace else { updateCurrentTask(with: .failure) runNextTask() return @@ -282,7 +278,7 @@ class SpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol { } private func inviteNextUserByEmail() { - guard let createdSpace = self.createdSpace, let room = createdSpace.room else { + guard let createdSpace = createdSpace, let room = createdSpace.room else { updateCurrentTask(with: .failure) runNextTask() return @@ -291,7 +287,7 @@ class SpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol { currentSubTaskIndex += 1 guard currentSubTaskIndex < tasks[currentTaskIndex].subTasks.count else { - let isSuccess = tasks[currentTaskIndex].subTasks.reduce(true, { $0 && $1.state == .success }) + let isSuccess = tasks[currentTaskIndex].subTasks.reduce(true) { $0 && $1.state == .success } updateCurrentTask(with: isSuccess ? .success : .failure) runNextTask() return @@ -306,7 +302,7 @@ class SpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol { } private func addNextExistingRoom() { - guard let createdSpace = self.createdSpace else { + guard let createdSpace = createdSpace else { updateCurrentTask(with: .failure) runNextTask() return @@ -315,7 +311,7 @@ class SpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol { currentSubTaskIndex += 1 guard currentSubTaskIndex < tasks[currentTaskIndex].subTasks.count else { - let isSuccess = tasks[currentTaskIndex].subTasks.reduce(true, { $0 && $1.state == .success }) + let isSuccess = tasks[currentTaskIndex].subTasks.reduce(true) { $0 && $1.state == .success } updateCurrentTask(with: isSuccess ? .success : .failure) runNextTask() return @@ -351,6 +347,6 @@ class SpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol { } tasks[currentTaskIndex].state = state - self.tasksSubject.send(tasks) + tasksSubject.send(tasks) } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/Mock/MockSpaceCreationPostProcessScreenState.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/Mock/MockSpaceCreationPostProcessScreenState.swift index a8220d8cc..27c003b71 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/Mock/MockSpaceCreationPostProcessScreenState.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/Mock/MockSpaceCreationPostProcessScreenState.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationPostProcess SpaceCreationPostProcess -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,6 @@ import SwiftUI enum MockSpaceCreationPostProcessScreenState: MockScreenState { static var screenStates: [MockScreenState] = [MockSpaceCreationPostProcessScreenState.running, MockSpaceCreationPostProcessScreenState.done, MockSpaceCreationPostProcessScreenState.doneWithError] - // A case for each state you want to represent // with specific, minimal associated data that will allow you // mock that screen. @@ -38,7 +37,7 @@ enum MockSpaceCreationPostProcessScreenState: MockScreenState { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let service: MockSpaceCreationPostProcessService switch self { case .running: diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/Mock/MockSpaceCreationPostProcessService.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/Mock/MockSpaceCreationPostProcessService.swift index 4e10a9abd..514b35fab 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/Mock/MockSpaceCreationPostProcessService.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/Mock/MockSpaceCreationPostProcessService.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationPostProcess SpaceCreationPostProcess -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,12 +16,11 @@ // limitations under the License. // -import Foundation import Combine +import Foundation import UIKit class MockSpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol { - static let defaultTasks: [SpaceCreationPostProcessTask] = [ SpaceCreationPostProcessTask(type: .createSpace, title: "Space creation", state: .success), SpaceCreationPostProcessTask(type: .createRoom("Room#1"), title: "Room#1 creation", state: .failure), @@ -53,22 +52,22 @@ class MockSpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtoc var tasksSubject: CurrentValueSubject<[SpaceCreationPostProcessTask], Never> private(set) var createdSpaceId: String? var avatar: AvatarInput { - return AvatarInput(mxContentUri: nil, matrixItemId: "", displayName: "Some space") + AvatarInput(mxContentUri: nil, matrixItemId: "", displayName: "Some space") } + var avatarImage: UIImage? { - return nil + nil } init( tasks: [SpaceCreationPostProcessTask] = defaultTasks ) { - self.tasksSubject = CurrentValueSubject<[SpaceCreationPostProcessTask], Never>(tasks) + tasksSubject = CurrentValueSubject<[SpaceCreationPostProcessTask], Never>(tasks) } func simulateUpdate(tasks: [SpaceCreationPostProcessTask]) { - self.tasksSubject.send(tasks) + tasksSubject.send(tasks) } - func run() { - } + func run() { } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/SpaceCreationPostProcessServiceProtocol.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/SpaceCreationPostProcessServiceProtocol.swift index 7ff9e73cf..e9e4e711b 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/SpaceCreationPostProcessServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/SpaceCreationPostProcessServiceProtocol.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationPostProcess SpaceCreationPostProcess -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation import UIKit protocol SpaceCreationPostProcessServiceProtocol: AnyObject { diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Test/UI/SpaceCreationPostProcessUITests.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Test/UI/SpaceCreationPostProcessUITests.swift index 75c624772..4bfcbc424 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Test/UI/SpaceCreationPostProcessUITests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Test/UI/SpaceCreationPostProcessUITests.swift @@ -16,8 +16,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class SpaceCreationPostProcessUITests: MockScreenTestCase { // No tests have been implemented diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Test/Unit/SpaceCreationPostProcessViewModelTests.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Test/Unit/SpaceCreationPostProcessViewModelTests.swift index cb274b1ba..6859b9565 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Test/Unit/SpaceCreationPostProcessViewModelTests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Test/Unit/SpaceCreationPostProcessViewModelTests.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationPostProcess SpaceCreationPostProcess -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,13 +16,12 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI class SpaceCreationPostProcessViewModelTests: XCTestCase { - var service: MockSpaceCreationPostProcessService! var viewModel: SpaceCreationPostProcessViewModelProtocol! var context: SpaceCreationPostProcessViewModelType.Context! diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/View/SpaceCreationPostProcess.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/View/SpaceCreationPostProcess.swift index 3c5ad177e..38f99a935 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/View/SpaceCreationPostProcess.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/View/SpaceCreationPostProcess.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationPostProcess SpaceCreationPostProcess -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import SwiftUI struct SpaceCreationPostProcess: View { - // MARK: - Properties // MARK: Private @@ -44,7 +43,7 @@ struct SpaceCreationPostProcess: View { .navigationBarHidden(true) .background(theme.colors.background.ignoresSafeArea()) .frame(maxHeight: .infinity) - .onAppear() { + .onAppear { viewModel.send(viewAction: .runTasks) } } @@ -62,7 +61,7 @@ struct SpaceCreationPostProcess: View { @ViewBuilder private var tasksList: some View { VStack(alignment: .leading, spacing: 11) { - ForEach(viewModel.viewState.tasks.indices) { index in + ForEach(viewModel.viewState.tasks.indices, id: \.self) { index in SpaceCreationPostProcessItem(title: viewModel.viewState.tasks[index].title, state: viewModel.viewState.tasks[index].state) } } @@ -85,7 +84,7 @@ struct SpaceCreationPostProcess: View { private var avatarView: some View { ZStack { SpaceAvatarImage(mxContentUri: viewModel.viewState.avatar.mxContentUri, matrixItemId: viewModel.viewState.avatar.matrixItemId, displayName: viewModel.viewState.avatar.displayName, size: .xLarge) - .padding(6) + .padding(6) if let image = viewModel.viewState.avatarImage { Image(uiImage: image) .resizable() @@ -95,7 +94,6 @@ struct SpaceCreationPostProcess: View { } } } - } // MARK: - Previews diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/View/SpaceCreationPostProcessItem.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/View/SpaceCreationPostProcessItem.swift index 8fe554b10..58f69884f 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/View/SpaceCreationPostProcessItem.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/View/SpaceCreationPostProcessItem.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModel.swift index 57a736b09..25b1a85c4 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModel.swift @@ -16,16 +16,13 @@ // limitations under the License. // -import SwiftUI import Combine - - +import SwiftUI typealias SpaceCreationPostProcessViewModelType = StateStoreViewModel + SpaceCreationPostProcessStateAction, + SpaceCreationPostProcessViewAction> class SpaceCreationPostProcessViewModel: SpaceCreationPostProcessViewModelType, SpaceCreationPostProcessViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -40,7 +37,7 @@ class SpaceCreationPostProcessViewModel: SpaceCreationPostProcessViewModelType, // MARK: - Setup static func makeSpaceCreationPostProcessViewModel(spaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol) -> SpaceCreationPostProcessViewModelProtocol { - return SpaceCreationPostProcessViewModel(spaceCreationPostProcessService: spaceCreationPostProcessService) + SpaceCreationPostProcessViewModel(spaceCreationPostProcessService: spaceCreationPostProcessService) } private init(spaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol) { @@ -55,8 +52,8 @@ class SpaceCreationPostProcessViewModel: SpaceCreationPostProcessViewModelType, avatar: spaceCreationPostProcessService.avatar, avatarImage: spaceCreationPostProcessService.avatarImage, tasks: tasks, - isFinished: tasks.first?.state == .failure || tasks.reduce(true, { result, task in result && task.isFinished }), - errorCount: tasks.reduce(0, { result, task in result + (task.state == .failure ? 1 : 0) }) + isFinished: tasks.first?.state == .failure || tasks.reduce(true) { result, task in result && task.isFinished }, + errorCount: tasks.reduce(0) { result, task in result + (task.state == .failure ? 1 : 0) } ) } @@ -74,7 +71,7 @@ class SpaceCreationPostProcessViewModel: SpaceCreationPostProcessViewModelType, return } - if state.isFinished && state.errorCount == 0 { + if state.isFinished, state.errorCount == 0 { guard let spaceId = self.spaceCreationPostProcessService.createdSpaceId else { self.cancel() return @@ -108,11 +105,11 @@ class SpaceCreationPostProcessViewModel: SpaceCreationPostProcessViewModelType, switch action { case .updateTasks(let tasks): state.tasks = tasks - state.isFinished = tasks.first?.state == .failure || tasks.reduce(true, { result, task in result && task.isFinished }) - state.errorCount = tasks.reduce(0, { result, task in result + (task.state == .failure ? 1 : 0) }) + state.isFinished = tasks.first?.state == .failure || tasks.reduce(true) { result, task in result && task.isFinished } + state.errorCount = tasks.reduce(0) { result, task in result + (task.state == .failure ? 1 : 0) } } - NotificationCenter.default.post(name: SpaceCreationPostProcessViewModel.didUpdate, object: nil, userInfo: [SpaceCreationPostProcessViewModel.newStateKey : state]) + NotificationCenter.default.post(name: SpaceCreationPostProcessViewModel.didUpdate, object: nil, userInfo: [SpaceCreationPostProcessViewModel.newStateKey: state]) UILog.debug("[SpaceCreationPostProcessViewModel] reducer with action \(action) produced state: \(state)") } @@ -131,6 +128,7 @@ class SpaceCreationPostProcessViewModel: SpaceCreationPostProcessViewModelType, } // MARK: - MXSpaceService notification constants + extension SpaceCreationPostProcessViewModel { /// Posted once the process is finished public static let didUpdate = Notification.Name("SpaceCreationPostProcessViewModelDidUpdate") diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModelProtocol.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModelProtocol.swift index f71785b35..4d5ea2713 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModelProtocol.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationPostProcess SpaceCreationPostProcess -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import Foundation protocol SpaceCreationPostProcessViewModelProtocol { - var completion: ((SpaceCreationPostProcessViewModelResult) -> Void)? { get set } static func makeSpaceCreationPostProcessViewModel(spaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol) -> SpaceCreationPostProcessViewModelProtocol var context: SpaceCreationPostProcessViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Coordinator/SpaceCreationRoomsCoordinator.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Coordinator/SpaceCreationRoomsCoordinator.swift index e3da21122..2e94feec2 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Coordinator/SpaceCreationRoomsCoordinator.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Coordinator/SpaceCreationRoomsCoordinator.swift @@ -17,11 +17,10 @@ */ import Foundation -import UIKit import SwiftUI +import UIKit final class SpaceCreationRoomsCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -68,6 +67,6 @@ final class SpaceCreationRoomsCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.spaceCreationRoomsHostingController + spaceCreationRoomsHostingController } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Coordinator/SpaceCreationRoomsCoordinatorParameters.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Coordinator/SpaceCreationRoomsCoordinatorParameters.swift index 6e095f71b..04a85dcac 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Coordinator/SpaceCreationRoomsCoordinatorParameters.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Coordinator/SpaceCreationRoomsCoordinatorParameters.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationRooms SpaceCreationRooms -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsCoordinatorAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsCoordinatorAction.swift index 749d87bd1..7455b5aa6 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsCoordinatorAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsCoordinatorAction.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsPresence.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsPresence.swift index 3790e6207..052148c8d 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsPresence.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsPresence.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationRooms SpaceCreationRooms -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsStateAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsStateAction.swift index 3515e1250..8cfcd7660 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsStateAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsStateAction.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationRooms SpaceCreationRooms -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,5 +18,4 @@ import Foundation -enum SpaceCreationRoomsStateAction { -} +enum SpaceCreationRoomsStateAction { } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsViewAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsViewAction.swift index c7ed6ce24..a1586ca41 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsViewAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsViewAction.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationRooms SpaceCreationRooms -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsViewModelBindings.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsViewModelBindings.swift index f9d2ad6e7..e5f17b85a 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsViewModelBindings.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsViewModelBindings.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsViewModelResult.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsViewModelResult.swift index 8368baacc..96c036877 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsViewModelResult.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsViewModelResult.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationRooms SpaceCreationRooms -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsViewState.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsViewState.swift index 81bf820ec..7d469c314 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsViewState.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsViewState.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationRooms SpaceCreationRooms -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Service/Mock/MockSpaceCreationRoomsScreenState.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Service/Mock/MockSpaceCreationRoomsScreenState.swift index fd5919029..3ef06a64f 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Service/Mock/MockSpaceCreationRoomsScreenState.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Service/Mock/MockSpaceCreationRoomsScreenState.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationRooms SpaceCreationRooms -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,7 +39,7 @@ enum MockSpaceCreationRoomsScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let creationParams = SpaceCreationParameters() switch self { case .defaultValues: break diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Test/UI/SpaceCreationRoomsUITests.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Test/UI/SpaceCreationRoomsUITests.swift index 2dedafb37..90cdff13b 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Test/UI/SpaceCreationRoomsUITests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Test/UI/SpaceCreationRoomsUITests.swift @@ -16,8 +16,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class SpaceCreationRoomsUITests: MockScreenTestCase { func testDefaultValues() { diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Test/Unit/SpaceCreationRoomsViewModelTests.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Test/Unit/SpaceCreationRoomsViewModelTests.swift index c09e85826..3960a2d05 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Test/Unit/SpaceCreationRoomsViewModelTests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Test/Unit/SpaceCreationRoomsViewModelTests.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationRooms SpaceCreationRooms -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,8 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/View/SpaceCreationRooms.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/View/SpaceCreationRooms.swift index 8137dce9f..fbb86d7f4 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/View/SpaceCreationRooms.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/View/SpaceCreationRooms.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationRooms SpaceCreationRooms -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import SwiftUI struct SpaceCreationRooms: View { - // MARK: - Properties // MARK: Private @@ -50,7 +49,7 @@ struct SpaceCreationRooms: View { VStack { GeometryReader { reader in ScrollView { - ScrollViewReader { scrollViewReader in + ScrollViewReader { _ in VStack(spacing: 20) { Text(VectorL10n.spacesCreationNewRoomsTitle) .multilineTextAlignment(.center) @@ -61,8 +60,8 @@ struct SpaceCreationRooms: View { .font(theme.fonts.body) .foregroundColor(theme.colors.secondaryContent) Spacer() - ForEach(viewModel.rooms.indices) { index in - RoundedBorderTextField(title: VectorL10n.spacesCreationNewRoomsRoomNameTitle, placeHolder: viewModel.rooms[index].defaultName, text: $viewModel.rooms[index].name, configuration: UIKitTextInputConfiguration( returnKeyType: index < viewModel.rooms.endIndex - 1 ? .next : .done)) + ForEach(viewModel.rooms.indices, id: \.self) { index in + RoundedBorderTextField(title: VectorL10n.spacesCreationNewRoomsRoomNameTitle, placeHolder: viewModel.rooms[index].defaultName, text: $viewModel.rooms[index].name, configuration: UIKitTextInputConfiguration(returnKeyType: index < viewModel.rooms.endIndex - 1 ? .next : .done)) .accessibility(identifier: "roomTextField") } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModel.swift index dfb989a80..034c7a427 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModel.swift @@ -16,16 +16,13 @@ // limitations under the License. // -import SwiftUI import Combine - - +import SwiftUI typealias SpaceCreationRoomsViewModelType = StateStoreViewModel + SpaceCreationRoomsStateAction, + SpaceCreationRoomsViewAction> class SpaceCreationRoomsViewModel: SpaceCreationRoomsViewModelType, SpaceCreationRoomsViewModelProtocol { - // MARK: - Setup // MARK: Private @@ -51,7 +48,6 @@ class SpaceCreationRoomsViewModel: SpaceCreationRoomsViewModelType, SpaceCreatio ) } - // MARK: - Public override func process(viewAction: SpaceCreationRoomsViewAction) { @@ -65,13 +61,12 @@ class SpaceCreationRoomsViewModel: SpaceCreationRoomsViewModelType, SpaceCreatio } } - override class func reducer(state: inout SpaceCreationRoomsViewState, action: SpaceCreationRoomsStateAction) { - } + override class func reducer(state: inout SpaceCreationRoomsViewState, action: SpaceCreationRoomsStateAction) { } // MARK: - Private private func done() { - self.creationParameters.newRooms = self.context.rooms + creationParameters.newRooms = context.rooms callback?(.done) } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModelProtocol.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModelProtocol.swift index f7bbd263b..dac8fbe83 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModelProtocol.swift @@ -1,6 +1,6 @@ // File created from SimpleUserProfileExample // $ createScreen.sh Spaces/SpaceCreation/SpaceCreationRooms SpaceCreationRooms -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import Foundation protocol SpaceCreationRoomsViewModelProtocol { - var callback: ((SpaceCreationRoomsViewModelResult) -> Void)? { get set } var context: SpaceCreationRoomsViewModelType.Context { get } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Coordinator/SpaceCreationSettingsCoordinator.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Coordinator/SpaceCreationSettingsCoordinator.swift index 9461faaae..5077e4657 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Coordinator/SpaceCreationSettingsCoordinator.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Coordinator/SpaceCreationSettingsCoordinator.swift @@ -17,11 +17,10 @@ */ import Foundation -import UIKit import SwiftUI +import UIKit final class SpaceCreationSettingsCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -72,13 +71,12 @@ final class SpaceCreationSettingsCoordinator: Coordinator, Presentable { self.callback?(.back) case .pickImage(let sourceRect): self.pickImage(from: sourceRect) - break } } } func toPresentable() -> UIViewController { - return self.spaceCreationSettingsHostingController + spaceCreationSettingsHostingController } // MARK: - Private @@ -91,6 +89,7 @@ final class SpaceCreationSettingsCoordinator: Coordinator, Presentable { } // MARK: - SingleImagePickerPresenterDelegate + extension SpaceCreationSettingsCoordinator: SingleImagePickerPresenterDelegate { func singleImagePickerPresenter(_ presenter: SingleImagePickerPresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) { spaceCreationSettingsViewModel.updateAvatarImage(with: UIImage(data: imageData)) diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Coordinator/SpaceCreationSettingsCoordinatorParamaters.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Coordinator/SpaceCreationSettingsCoordinatorParamaters.swift index f79a0c749..5a865710e 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Coordinator/SpaceCreationSettingsCoordinatorParamaters.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Coordinator/SpaceCreationSettingsCoordinatorParamaters.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsAddressValidationStatus.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsAddressValidationStatus.swift index b7262a430..2457e1ae8 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsAddressValidationStatus.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsAddressValidationStatus.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsCoordinatorAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsCoordinatorAction.swift index b94d0d3c2..719b213f7 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsCoordinatorAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsCoordinatorAction.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsStateAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsStateAction.swift index 670015eae..33fa8b36c 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsStateAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsStateAction.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsViewAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsViewAction.swift index de2350fc9..c8d9dadcc 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsViewAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsViewAction.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsViewModelAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsViewModelAction.swift index 94b838e38..7eb605e8c 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsViewModelAction.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsViewModelAction.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsViewModelBindings.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsViewModelBindings.swift index 0a1757979..0dafd1f6c 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsViewModelBindings.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsViewModelBindings.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsViewState.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsViewState.swift index 663d968df..a61da6598 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsViewState.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsViewState.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,4 +31,3 @@ struct SpaceCreationSettingsViewState: BindableState { var avatarImage: UIImage? var bindings: SpaceCreationSettingsViewModelBindings } - diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/MatrixSDK/SpaceCreationSettingsService.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/MatrixSDK/SpaceCreationSettingsService.swift index bf5835b62..9b7e2d846 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/MatrixSDK/SpaceCreationSettingsService.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/MatrixSDK/SpaceCreationSettingsService.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,12 +16,11 @@ // limitations under the License. // -import Foundation import Combine +import Foundation import MatrixSDK class SpaceCreationSettingsService: SpaceCreationSettingsServiceProtocol { - // MARK: - Properties var roomName: String { @@ -30,6 +29,7 @@ class SpaceCreationSettingsService: SpaceCreationSettingsServiceProtocol { updateAvatar() } } + var userDefinedAddress: String? { didSet { validateAddress() @@ -45,10 +45,12 @@ class SpaceCreationSettingsService: SpaceCreationSettingsServiceProtocol { validateAddress() } } - private var lastValidatedAddress: String = "" + + private var lastValidatedAddress = "" private var currentAddress: String? { - return self.userDefinedAddress?.count ?? 0 > 0 ? self.userDefinedAddress : defaultAddress + userDefinedAddress?.count ?? 0 > 0 ? userDefinedAddress : defaultAddress } + private var currentOperation: MXHTTPOperation? // MARK: Public @@ -69,14 +71,14 @@ class SpaceCreationSettingsService: SpaceCreationSettingsServiceProtocol { init(roomName: String, userDefinedAddress: String?, session: MXSession) { self.session = session - self.defaultAddress = "" - self.defaultAddressSubject = CurrentValueSubject(defaultAddress) + defaultAddress = "" + defaultAddressSubject = CurrentValueSubject(defaultAddress) self.roomName = roomName - self.addressValidationSubject = CurrentValueSubject(.none("#")) - self.avatarViewDataSubject = CurrentValueSubject(AvatarInput(mxContentUri: userDefinedAddress, matrixItemId: "", displayName: roomName)) + addressValidationSubject = CurrentValueSubject(.none("#")) + avatarViewDataSubject = CurrentValueSubject(AvatarInput(mxContentUri: userDefinedAddress, matrixItemId: "", displayName: roomName)) - self.updateDefaultAddress() - self.validateAddress() + updateDefaultAddress() + validateAddress() } deinit { @@ -89,7 +91,7 @@ class SpaceCreationSettingsService: SpaceCreationSettingsServiceProtocol { // MARK: Private private func updateAvatar() { - self.avatarViewDataSubject.send(AvatarInput(mxContentUri: currentAddress, matrixItemId: "", displayName: roomName)) + avatarViewDataSubject.send(AvatarInput(mxContentUri: currentAddress, matrixItemId: "", displayName: roomName)) } private func updateDefaultAddress() { @@ -100,7 +102,7 @@ class SpaceCreationSettingsService: SpaceCreationSettingsServiceProtocol { currentOperation?.cancel() currentOperation = nil - guard let userDefinedAddress = self.userDefinedAddress, !userDefinedAddress.isEmpty else { + guard let userDefinedAddress = userDefinedAddress, !userDefinedAddress.isEmpty else { let fullAddress = MXTools.fullLocalAlias(from: defaultAddress, with: session) if defaultAddress.isEmpty { diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/Mock/MockSpaceCreationSettingsScreenState.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/Mock/MockSpaceCreationSettingsScreenState.swift index cf18234d6..8738289a9 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/Mock/MockSpaceCreationSettingsScreenState.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/Mock/MockSpaceCreationSettingsScreenState.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ import Foundation import SwiftUI - /// Using an enum for the screen allows you define the different state cases with /// the relevant associated data for each case. enum MockSpaceCreationSettingsScreenState: MockScreenState, CaseIterable { @@ -40,7 +39,7 @@ enum MockSpaceCreationSettingsScreenState: MockScreenState, CaseIterable { let creationParameters = SpaceCreationParameters() creationParameters.name = "Fake" - let service: MockSpaceCreationSettingsService = MockSpaceCreationSettingsService() + let service = MockSpaceCreationSettingsService() switch self { case .privateSpace: creationParameters.isPublic = false @@ -60,7 +59,7 @@ enum MockSpaceCreationSettingsScreenState: MockScreenState, CaseIterable { return ( [service, viewModel], AnyView(SpaceCreationSettings(viewModel: viewModel.context) - .addDependency(MockAvatarService.example)) + .addDependency(MockAvatarService.example)) ) } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/Mock/MockSpaceCreationSettingsService.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/Mock/MockSpaceCreationSettingsService.swift index 5e9fa46d4..616381292 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/Mock/MockSpaceCreationSettingsService.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/Mock/MockSpaceCreationSettingsService.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,19 +16,17 @@ // limitations under the License. // -import Foundation import Combine +import Foundation class MockSpaceCreationSettingsService: SpaceCreationSettingsServiceProtocol { - - var addressValidationSubject: CurrentValueSubject var avatarViewDataSubject: CurrentValueSubject var defaultAddressSubject: CurrentValueSubject var spaceAddress: String? var roomName: String var userDefinedAddress: String? - var isAddressValid: Bool = true + var isAddressValid = true init() { roomName = "Fake" @@ -38,7 +36,7 @@ class MockSpaceCreationSettingsService: SpaceCreationSettingsServiceProtocol { } func simulateUpdate(addressValidationStatus: SpaceCreationSettingsAddressValidationStatus) { - self.addressValidationSubject.value = addressValidationStatus + addressValidationSubject.value = addressValidationStatus } // func simulateUpdate() diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/SpaceCreationSettingsServiceProtocol.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/SpaceCreationSettingsServiceProtocol.swift index 7b594fecc..35a176ed8 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/SpaceCreationSettingsServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/SpaceCreationSettingsServiceProtocol.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation protocol SpaceCreationSettingsServiceProtocol: AnyObject { var defaultAddressSubject: CurrentValueSubject { get } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Test/UI/SpaceCreationSettingsUITests.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Test/UI/SpaceCreationSettingsUITests.swift index f1f0e705d..685488026 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Test/UI/SpaceCreationSettingsUITests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Test/UI/SpaceCreationSettingsUITests.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class SpaceCreationSettingsUITests: MockScreenTestCase { func testPrivateSpace() { diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Test/Unit/SpaceCreationSettingsViewModelTests.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Test/Unit/SpaceCreationSettingsViewModelTests.swift index dcefa9fb7..4a0e74afd 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Test/Unit/SpaceCreationSettingsViewModelTests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Test/Unit/SpaceCreationSettingsViewModelTests.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,20 +16,18 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI class SpaceCreationSettingsViewModelTests: XCTestCase { - let creationParameters = SpaceCreationParameters() var service: MockSpaceCreationSettingsService! var viewModel: SpaceCreationSettingsViewModel! var context: SpaceCreationSettingsViewModel.Context! var cancellables = Set() - override func setUpWithError() throws { creationParameters.name = "Fake" creationParameters.isPublic = true diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/View/SpaceCreationSettings.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/View/SpaceCreationSettings.swift index d8e697dcd..9135bf8cc 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/View/SpaceCreationSettings.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/View/SpaceCreationSettings.swift @@ -16,11 +16,10 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI struct SpaceCreationSettings: View { - // MARK: - Properties @ObservedObject var viewModel: SpaceCreationSettingsViewModel.Context @@ -69,8 +68,8 @@ struct SpaceCreationSettings: View { ZStack(alignment: .bottomTrailing) { GeometryReader { reader in ZStack { - SpaceAvatarImage(mxContentUri: viewModel.viewState.avatar.mxContentUri, matrixItemId: viewModel.viewState.avatar.matrixItemId, displayName: viewModel.viewState.avatar.displayName, size: .xxLarge) - .padding(6) + SpaceAvatarImage(mxContentUri: viewModel.viewState.avatar.mxContentUri, matrixItemId: viewModel.viewState.avatar.matrixItemId, displayName: viewModel.viewState.avatar.displayName, size: .xxLarge) + .padding(6) if let image = viewModel.viewState.avatarImage { Image(uiImage: image) .resizable() @@ -79,10 +78,10 @@ struct SpaceCreationSettings: View { .clipShape(RoundedRectangle(cornerRadius: 8)) } }.padding(10) - .gesture(TapGesture().onEnded { _ in - ResponderManager.resignFirstResponder() - viewModel.send(viewAction: .pickImage(reader.frame(in: .global))) - }) + .gesture(TapGesture().onEnded { _ in + ResponderManager.resignFirstResponder() + viewModel.send(viewAction: .pickImage(reader.frame(in: .global))) + }) } Image(uiImage: Asset.Images.spaceCreationCamera.image) .renderingMode(.template) @@ -95,21 +94,21 @@ struct SpaceCreationSettings: View { @ViewBuilder private var formView: some View { - GeometryReader { geometryReader in + GeometryReader { _ in ScrollView { ScrollViewReader { scrollViewReader in VStack { headerView Spacer() avatarView - Spacer().frame(height:40) - RoundedBorderTextField(title: VectorL10n.createRoomPlaceholderName, placeHolder: "", text: $viewModel.roomName, footerText: viewModel.viewState.roomNameError, isError: true, isFirstResponder: false, configuration: UIKitTextInputConfiguration( returnKeyType: .next), onTextChanged: { newText in + Spacer().frame(height: 40) + RoundedBorderTextField(title: VectorL10n.createRoomPlaceholderName, placeHolder: "", text: $viewModel.roomName, footerText: viewModel.viewState.roomNameError, isError: true, isFirstResponder: false, configuration: UIKitTextInputConfiguration(returnKeyType: .next), onTextChanged: { newText in viewModel.send(viewAction: .nameChanged(newText)) }) .id("nameTextField") .padding(.horizontal, 2) .padding(.bottom, 20) - RoundedBorderTextEditor(title: nil, placeHolder: VectorL10n.spaceTopic, text: $viewModel.topic, textMaxHeight: 72, error: nil, onTextChanged: { + RoundedBorderTextEditor(title: nil, placeHolder: VectorL10n.spaceTopic, text: $viewModel.topic, textMaxHeight: 72, error: nil, onTextChanged: { newText in viewModel.send(viewAction: .topicChanged(newText)) }, onEditingChanged: { editing in @@ -121,7 +120,7 @@ struct SpaceCreationSettings: View { .padding(.horizontal, 2) .padding(.bottom, viewModel.viewState.showRoomAddress ? 20 : 3) if viewModel.viewState.showRoomAddress { - RoundedBorderTextField(title: VectorL10n.spacesCreationAddress, placeHolder: "# \(viewModel.viewState.defaultAddress)", text: $viewModel.address, footerText: viewModel.viewState.addressMessage, isError: !viewModel.viewState.isAddressValid, configuration: UIKitTextInputConfiguration(keyboardType: .URL, returnKeyType: .done, autocapitalizationType: .none), onTextChanged: { + RoundedBorderTextField(title: VectorL10n.spacesCreationAddress, placeHolder: "# \(viewModel.viewState.defaultAddress)", text: $viewModel.address, footerText: viewModel.viewState.addressMessage, isError: !viewModel.viewState.isAddressValid, configuration: UIKitTextInputConfiguration(keyboardType: .URL, returnKeyType: .done, autocapitalizationType: .none), onTextChanged: { newText in viewModel.send(viewAction: .addressChanged(newText)) }) diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModel.swift index 742504b41..f3aca8133 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModel.swift @@ -16,15 +16,14 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias SpaceCreationSettingsViewModelType = StateStoreViewModel + SpaceCreationSettingsStateAction, + SpaceCreationSettingsViewAction> class SpaceCreationSettingsViewModel: SpaceCreationSettingsViewModelType, SpaceCreationSettingsViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -67,7 +66,8 @@ class SpaceCreationSettingsViewModel: SpaceCreationSettingsViewModelType, SpaceC let bindings = SpaceCreationSettingsViewModelBindings( roomName: creationParameters.name ?? "", topic: creationParameters.topic ?? "", - address: creationParameters.userDefinedAddress ?? "") + address: creationParameters.userDefinedAddress ?? "" + ) return SpaceCreationSettingsViewState( title: creationParameters.isPublic ? VectorL10n.spacesCreationPublicSpaceTitle : VectorL10n.spacesCreationPrivateSpaceTitle, @@ -78,7 +78,8 @@ class SpaceCreationSettingsViewModel: SpaceCreationSettingsViewModelType, SpaceC isAddressValid: validationStatus.isValid, avatar: AvatarInput(mxContentUri: nil, matrixItemId: "", displayName: nil), avatarImage: creationParameters.userSelectedAvatar, - bindings: bindings) + bindings: bindings + ) } // MARK: - Public diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModelProtocol.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModelProtocol.swift index 9802bca02..aa62cc895 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModelProtocol.swift @@ -1,6 +1,6 @@ // File created from TemplateAdvancedRoomsExample // $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/Coordinator/SpaceSelectorBottomSheetCoordinator.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/Coordinator/SpaceSelectorBottomSheetCoordinator.swift index 7237b0aa3..a6c7539c2 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/Coordinator/SpaceSelectorBottomSheetCoordinator.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/Coordinator/SpaceSelectorBottomSheetCoordinator.swift @@ -39,7 +39,6 @@ struct SpaceSelectorBottomSheetCoordinatorParameters { } final class SpaceSelectorBottomSheetCoordinator: NSObject, Coordinator, Presentable { - // MARK: - Properties private let parameters: SpaceSelectorBottomSheetCoordinatorParameters @@ -61,11 +60,11 @@ final class SpaceSelectorBottomSheetCoordinator: NSObject, Coordinator, Presenta navigationRouter: NavigationRouterType = NavigationRouter(navigationController: RiotNavigationController())) { self.parameters = parameters self.navigationRouter = navigationRouter - self.spaceIdStack = [] + spaceIdStack = [] super.init() - self.setupNavigationRouter() + setupNavigationRouter() } // MARK: - Public @@ -76,7 +75,7 @@ final class SpaceSelectorBottomSheetCoordinator: NSObject, Coordinator, Presenta } func toPresentable() -> UIViewController { - return self.navigationRouter.toPresentable() + navigationRouter.toPresentable() } // MARK: - Private @@ -84,7 +83,7 @@ final class SpaceSelectorBottomSheetCoordinator: NSObject, Coordinator, Presenta private func setupNavigationRouter() { guard #available(iOS 15.0, *) else { return } - guard let sheetController = self.navigationRouter.toPresentable().sheetPresentationController else { + guard let sheetController = navigationRouter.toPresentable().sheetPresentationController else { MXLog.debug("[SpaceSelectorBottomSheetCoordinator] setup: no sheetPresentationController found") return } @@ -94,14 +93,14 @@ final class SpaceSelectorBottomSheetCoordinator: NSObject, Coordinator, Presenta sheetController.selectedDetentIdentifier = .medium sheetController.prefersScrollingExpandsWhenScrolledToEdge = true - self.navigationRouter.toPresentable().presentationController?.delegate = self + navigationRouter.toPresentable().presentationController?.delegate = self } private func push(_ coordinator: Coordinator & Presentable) { - if self.navigationRouter.modules.isEmpty { - self.navigationRouter.setRootModule(coordinator) + if navigationRouter.modules.isEmpty { + navigationRouter.setRootModule(coordinator) } else { - self.navigationRouter.push(coordinator.toPresentable(), animated: true) { [weak self] in + navigationRouter.push(coordinator.toPresentable(), animated: true) { [weak self] in guard let self = self else { return } self.remove(childCoordinator: coordinator) @@ -145,10 +144,10 @@ final class SpaceSelectorBottomSheetCoordinator: NSObject, Coordinator, Presenta coordinator.start() - self.add(childCoordinator: coordinator) + add(childCoordinator: coordinator) if let spaceId = parentSpaceId { - self.spaceIdStack.append(spaceId) + spaceIdStack.append(spaceId) } return coordinator @@ -166,13 +165,12 @@ final class SpaceSelectorBottomSheetCoordinator: NSObject, Coordinator, Presenta self.completion?(.spaceJoined(spaceId)) case .open, .cancel, .dismiss: self.navigationRouter.popModule(animated: true) - break } } coordinator.start() - self.add(childCoordinator: coordinator) + add(childCoordinator: coordinator) return coordinator } @@ -194,9 +192,7 @@ final class SpaceSelectorBottomSheetCoordinator: NSObject, Coordinator, Presenta // MARK: - UIAdaptivePresentationControllerDelegate extension SpaceSelectorBottomSheetCoordinator: UIAdaptivePresentationControllerDelegate { - func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { completion?(.cancel) } - } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/Coordinator/SpaceSelectorBottomSheetCoordinatorBridgePresenter.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/Coordinator/SpaceSelectorBottomSheetCoordinatorBridgePresenter.swift index b80be9e30..425063a87 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/Coordinator/SpaceSelectorBottomSheetCoordinatorBridgePresenter.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/Coordinator/SpaceSelectorBottomSheetCoordinatorBridgePresenter.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +29,6 @@ import Foundation /// Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator. @objcMembers final class SpaceSelectorBottomSheetCoordinatorBridgePresenter: NSObject { - // MARK: - Properties // MARK: Private @@ -82,7 +81,7 @@ final class SpaceSelectorBottomSheetCoordinatorBridgePresenter: NSObject { } func dismiss(animated: Bool, completion: (() -> Void)?) { - guard let coordinator = self.coordinator else { + guard let coordinator = coordinator else { return } coordinator.toPresentable().dismiss(animated: animated) { @@ -92,4 +91,3 @@ final class SpaceSelectorBottomSheetCoordinatorBridgePresenter: NSObject { } } } - diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Coordinator/SpaceSelectorCoordinator.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Coordinator/SpaceSelectorCoordinator.swift index 8c13f0216..771ad0b8f 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Coordinator/SpaceSelectorCoordinator.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Coordinator/SpaceSelectorCoordinator.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI struct SpaceSelectorCoordinatorParameters { let session: MXSession @@ -38,7 +38,6 @@ struct SpaceSelectorCoordinatorParameters { } final class SpaceSelectorCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -99,7 +98,7 @@ final class SpaceSelectorCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.hostingViewController + hostingViewController } // MARK: - Private diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/MockSpaceSelectorScreenState.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/MockSpaceSelectorScreenState.swift index f267ef6a9..22a613be2 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/MockSpaceSelectorScreenState.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/MockSpaceSelectorScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,7 +38,7 @@ enum MockSpaceSelectorScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let service: MockSpaceSelectorService switch self { case .emptyList: diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Service/MatrixSDK/SpaceSelectorService.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Service/MatrixSDK/SpaceSelectorService.swift index 1ba496e9b..4db35bf69 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Service/MatrixSDK/SpaceSelectorService.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Service/MatrixSDK/SpaceSelectorService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,12 +14,11 @@ // limitations under the License. // -import Foundation import Combine +import Foundation import MatrixSDK class SpaceSelectorService: SpaceSelectorServiceProtocol { - // MARK: - Properties // MARK: Private @@ -65,7 +64,7 @@ class SpaceSelectorService: SpaceSelectorServiceProtocol { var itemList: [SpaceSelectorListItemData] = [] itemList.append(contentsOf: invitedSpaces) - if showHomeSpace && parentSpaceId == nil { + if showHomeSpace, parentSpaceId == nil { itemList.append(SpaceSelectorListItemData(id: SpaceSelectorConstants.homeSpaceId, icon: Asset.Images.sideMenuActionIconFeedback.image, displayName: VectorL10n.allChatsTitle, isJoined: true)) } itemList.append(contentsOf: joinedSpaces) @@ -93,14 +92,14 @@ class SpaceSelectorService: SpaceSelectorServiceProtocol { self.session = session self.parentSpaceId = parentSpaceId self.showHomeSpace = showHomeSpace - self.spaceListSubject = CurrentValueSubject([]) - self.parentSpaceNameSubject = CurrentValueSubject(nil) + spaceListSubject = CurrentValueSubject([]) + parentSpaceNameSubject = CurrentValueSubject(nil) self.selectedSpaceId = selectedSpaceId spaceListSubject.send(spaceList) parentSpaceNameSubject.send(parentSpaceName) - NotificationCenter.default.addObserver(self, selector: #selector(self.spaceServiceDidUpdate), name: MXSpaceService.didBuildSpaceGraph, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(spaceServiceDidUpdate), name: MXSpaceService.didBuildSpaceGraph, object: nil) } @objc private func spaceServiceDidUpdate() { @@ -108,7 +107,7 @@ class SpaceSelectorService: SpaceSelectorServiceProtocol { } } -fileprivate extension SpaceSelectorListItemData { +private extension SpaceSelectorListItemData { static func itemData(with space: MXSpace, notificationCounter: MXSpaceNotificationCounter) -> SpaceSelectorListItemData? { guard let summary = space.summary else { return nil @@ -116,7 +115,7 @@ fileprivate extension SpaceSelectorListItemData { let notificationState = notificationCounter.notificationState(forSpaceWithId: space.spaceId) - return SpaceSelectorListItemData(id:summary.roomId, + return SpaceSelectorListItemData(id: summary.roomId, avatar: summary.room.avatarData, displayName: summary.displayname, notificationCount: notificationState?.groupMissedDiscussionsCount ?? 0, diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Service/Mock/MockSpaceSelectorService.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Service/Mock/MockSpaceSelectorService.swift index 5a3435252..590a5b849 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Service/Mock/MockSpaceSelectorService.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Service/Mock/MockSpaceSelectorService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,12 +14,11 @@ // limitations under the License. // -import Foundation import Combine +import Foundation import UIKit class MockSpaceSelectorService: SpaceSelectorServiceProtocol { - static let homeItem = SpaceSelectorListItemData(id: SpaceSelectorConstants.homeSpaceId, avatar: nil, icon: UIImage(systemName: "house"), displayName: "All Chats", notificationCount: 0, highlightedNotificationCount: 0, hasSubItems: false) static let defaultSpaceList = [ homeItem, @@ -35,8 +34,8 @@ class MockSpaceSelectorService: SpaceSelectorServiceProtocol { var selectedSpaceId: String? init(spaceList: [SpaceSelectorListItemData] = defaultSpaceList, parentSpaceName: String? = nil, selectedSpaceId: String = SpaceSelectorConstants.homeSpaceId) { - self.spaceListSubject = CurrentValueSubject(spaceList) - self.parentSpaceNameSubject = CurrentValueSubject(parentSpaceName) + spaceListSubject = CurrentValueSubject(spaceList) + parentSpaceNameSubject = CurrentValueSubject(parentSpaceName) self.selectedSpaceId = selectedSpaceId } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Service/SpaceSelectorServiceProtocol.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Service/SpaceSelectorServiceProtocol.swift index 48ebec3e6..e5a17462b 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Service/SpaceSelectorServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Service/SpaceSelectorServiceProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation protocol SpaceSelectorServiceProtocol { var spaceListSubject: CurrentValueSubject<[SpaceSelectorListItemData], Never> { get } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorModels.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorModels.swift index 1a9f16bc5..7511044fb 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorModels.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -76,7 +76,7 @@ struct SpaceSelectorListItemData { } } -extension SpaceSelectorListItemData: Identifiable, Equatable {} +extension SpaceSelectorListItemData: Identifiable, Equatable { } enum SpaceSelectorViewModelResult { /// Cancel button has been presed diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModel.swift index 37b7bc787..bed0600ed 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModel.swift @@ -14,15 +14,14 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias SpaceSelectorViewModelType = StateStoreViewModel + Never, + SpaceSelectorViewAction> class SpaceSelectorViewModel: SpaceSelectorViewModelType, SpaceSelectorViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -36,7 +35,7 @@ class SpaceSelectorViewModel: SpaceSelectorViewModelType, SpaceSelectorViewModel // MARK: - Setup static func makeViewModel(service: SpaceSelectorServiceProtocol, showCancel: Bool) -> SpaceSelectorViewModelProtocol { - return SpaceSelectorViewModel(service: service, showCancel: showCancel) + SpaceSelectorViewModel(service: service, showCancel: showCancel) } private init(service: SpaceSelectorServiceProtocol, showCancel: Bool) { @@ -55,7 +54,7 @@ class SpaceSelectorViewModel: SpaceSelectorViewModelType, SpaceSelectorViewModel private func setupObservers() { service.spaceListSubject.sink { [weak self] spaceList in - self?.state.items = spaceList + self?.state.items = spaceList } .store(in: &cancellables) } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModelProtocol.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModelProtocol.swift index 79263720d..ed0f477ba 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol SpaceSelectorViewModelProtocol { - var completion: ((SpaceSelectorViewModelResult) -> Void)? { get set } static func makeViewModel(service: SpaceSelectorServiceProtocol, showCancel: Bool) -> SpaceSelectorViewModelProtocol var context: SpaceSelectorViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Test/UI/SpaceSelectorUITests.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Test/UI/SpaceSelectorUITests.swift index 020127ea3..4cbc0fcad 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Test/UI/SpaceSelectorUITests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Test/UI/SpaceSelectorUITests.swift @@ -14,16 +14,15 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class SpaceSelectorUITests: MockScreenTestCase { - func testInitialDisplay() { app.goToScreenWithIdentifier(MockSpaceSelectorScreenState.initialList.title) let disclosureButtons = app.buttons.matching(identifier: "disclosureButton").allElementsBoundByIndex - XCTAssertEqual(disclosureButtons.count, MockSpaceSelectorService.defaultSpaceList.filter { $0.hasSubItems }.count) + XCTAssertEqual(disclosureButtons.count, MockSpaceSelectorService.defaultSpaceList.filter(\.hasSubItems).count) let notificationBadges = app.staticTexts.matching(identifier: "notificationBadge").allElementsBoundByIndex let itemsWithNotifications = MockSpaceSelectorService.defaultSpaceList.filter { $0.notificationCount > 0 || !$0.isJoined } @@ -65,5 +64,4 @@ class SpaceSelectorUITests: MockScreenTestCase { XCTAssertEqual(app.staticTexts["emptyListPlaceholderMessage"].exists, exists) XCTAssertEqual(app.buttons["createSpaceButton"].exists, exists) } - } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Test/Unit/SpaceSelectorViewModelTests.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Test/Unit/SpaceSelectorViewModelTests.swift index 58d5f9304..9395349a9 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Test/Unit/SpaceSelectorViewModelTests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Test/Unit/SpaceSelectorViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,13 +14,12 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI class SpaceSelectorViewModelTests: XCTestCase { - var service: MockSpaceSelectorService! var viewModel: SpaceSelectorViewModelProtocol! var context: SpaceSelectorViewModelType.Context! @@ -37,5 +36,4 @@ class SpaceSelectorViewModelTests: XCTestCase { XCTAssertEqual(context.viewState.items, MockSpaceSelectorService.defaultSpaceList) XCTAssertEqual(context.viewState.navigationTitle, VectorL10n.spaceSelectorTitle) } - } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelector.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelector.swift index 115a51444..df9c52906 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelector.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelector.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct SpaceSelector: View { - // MARK: - Properties // MARK: Private @@ -53,12 +52,11 @@ struct SpaceSelector: View { notificationCount: item.notificationCount, highlightedNotificationCount: item.highlightedNotificationCount, disclosureAction: { - viewModel.send(viewAction: .spaceDisclosure(item)) + viewModel.send(viewAction: .spaceDisclosure(item)) + }) + .onTapGesture { + viewModel.send(viewAction: .spaceSelected(item)) } - ) - .onTapGesture { - viewModel.send(viewAction: .spaceSelected(item)) - } } } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelectorListRow.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelectorListRow.swift index aa60c5dd4..d9e67c9b9 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelectorListRow.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelectorListRow.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -70,7 +70,7 @@ struct SpaceSelectorListRow: View { .renderingMode(.template) .foregroundColor(theme.colors.secondaryContent) } - if hasSubItems && isJoined { + if hasSubItems, isJoined { Button { disclosureAction?() } label: { @@ -91,7 +91,7 @@ struct SpaceSelectorListRow: View { } private func badge(with text: String, color: Color) -> some View { - return Text(text) + Text(text) .multilineTextAlignment(.center) .foregroundColor(theme.colors.background) .font(theme.fonts.footnote) @@ -106,7 +106,6 @@ struct SpaceSelectorListRow: View { // MARK: - Previews struct SpaceSelectorListRow_Previews: PreviewProvider { - static var previews: some View { sampleView.theme(.light).preferredColorScheme(.light) sampleView.theme(.dark).preferredColorScheme(.dark) @@ -121,5 +120,4 @@ struct SpaceSelectorListRow_Previews: PreviewProvider { SpaceSelectorListRow(avatar: nil, icon: UIImage(systemName: "house"), displayName: "Space name", hasSubItems: true, isJoined: true, isSelected: true, notificationCount: 99, highlightedNotificationCount: 1, disclosureAction: nil) } } - } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinator.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinator.swift index b2d10971c..8e7da9792 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinator.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinator.swift @@ -24,7 +24,6 @@ enum SpaceSettingsModalCoordinatorAction { @objcMembers final class SpaceSettingsModalCoordinator: Coordinator { - // MARK: - Properties // MARK: Private @@ -36,7 +35,7 @@ final class SpaceSettingsModalCoordinator: Coordinator { } private var navigationRouter: NavigationRouterType { - return self.parameters.navigationRouter + parameters.navigationRouter } // MARK: Public @@ -50,31 +49,30 @@ final class SpaceSettingsModalCoordinator: Coordinator { init(parameters: SpaceSettingsModalCoordinatorParameters) { self.parameters = parameters - } + } // MARK: - Public - func start() { MXLog.debug("[SpaceSettingsModalCoordinator] did start.") - let rootCoordinator = self.createSpaceSettingsCoordinator() + let rootCoordinator = createSpaceSettingsCoordinator() rootCoordinator.start() - self.add(childCoordinator: rootCoordinator) + add(childCoordinator: rootCoordinator) - if self.navigationRouter.modules.isEmpty == false { - self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in + if navigationRouter.modules.isEmpty == false { + navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in self?.remove(childCoordinator: rootCoordinator) }) } else { - self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in + navigationRouter.setRootModule(rootCoordinator) { [weak self] in self?.remove(childCoordinator: rootCoordinator) } } } func toPresentable() -> UIViewController { - return self.navigationRouter.toPresentable() + navigationRouter.toPresentable() } // MARK: - Private @@ -82,7 +80,7 @@ final class SpaceSettingsModalCoordinator: Coordinator { func pushScreen(with coordinator: Coordinator & Presentable) { add(childCoordinator: coordinator) - self.navigationRouter.push(coordinator, animated: true, popCompletion: { [weak self] in + navigationRouter.push(coordinator, animated: true, popCompletion: { [weak self] in self?.remove(childCoordinator: coordinator) }) @@ -110,12 +108,12 @@ final class SpaceSettingsModalCoordinator: Coordinator { switch optionType { case .rooms: Analytics.shared.viewRoomTrigger = .spaceSettings - exploreRooms(ofSpaceWithId: self.parameters.spaceId) + exploreRooms(ofSpaceWithId: parameters.spaceId) case .members: Analytics.shared.viewRoomTrigger = .spaceSettings - showMembers(ofSpaceWithId: self.parameters.spaceId) + showMembers(ofSpaceWithId: parameters.spaceId) case .visibility: - showAccess(ofSpaceWithId: self.parameters.spaceId) + showAccess(ofSpaceWithId: parameters.spaceId) } } @@ -124,7 +122,7 @@ final class SpaceSettingsModalCoordinator: Coordinator { coordinator.delegate = self add(childCoordinator: coordinator) coordinator.start() - self.navigationRouter.present(coordinator.toPresentable(), animated: true) + navigationRouter.present(coordinator.toPresentable(), animated: true) } private func showMembers(ofSpaceWithId spaceId: String) { @@ -132,7 +130,7 @@ final class SpaceSettingsModalCoordinator: Coordinator { coordinator.delegate = self add(childCoordinator: coordinator) coordinator.start() - self.navigationRouter.present(coordinator.toPresentable(), animated: true) + navigationRouter.present(coordinator.toPresentable(), animated: true) } private func showAccess(ofSpaceWithId spaceId: String) { @@ -158,23 +156,25 @@ final class SpaceSettingsModalCoordinator: Coordinator { } add(childCoordinator: coordinator) coordinator.start() - self.navigationRouter.present(coordinator.toPresentable(), animated: true) + navigationRouter.present(coordinator.toPresentable(), animated: true) } } // MARK: - ExploreRoomCoordinatorDelegate + extension SpaceSettingsModalCoordinator: ExploreRoomCoordinatorDelegate { func exploreRoomCoordinatorDidComplete(_ coordinator: ExploreRoomCoordinatorType) { - self.navigationRouter.dismissModule(animated: true, completion: { + navigationRouter.dismissModule(animated: true, completion: { self.remove(childCoordinator: coordinator) }) } } // MARK: - SpaceMembersCoordinatorDelegate + extension SpaceSettingsModalCoordinator: SpaceMembersCoordinatorDelegate { func spaceMembersCoordinatorDidCancel(_ coordinator: SpaceMembersCoordinatorType) { - self.navigationRouter.dismissModule(animated: true, completion: { + navigationRouter.dismissModule(animated: true, completion: { self.remove(childCoordinator: coordinator) }) } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinatorBridgePresenter.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinatorBridgePresenter.swift index 5614f2d08..c76f0aac7 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinatorBridgePresenter.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinatorBridgePresenter.swift @@ -27,7 +27,6 @@ import UIKit /// Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator. @objcMembers final class SpaceSettingsModalCoordinatorBridgePresenter: NSObject { - // MARK: - Properties // MARK: Private @@ -75,7 +74,7 @@ final class SpaceSettingsModalCoordinatorBridgePresenter: NSObject { } func dismiss(animated: Bool, completion: (() -> Void)?) { - guard let coordinator = self.coordinator else { + guard let coordinator = coordinator else { return } coordinator.toPresentable().dismiss(animated: animated) { @@ -91,9 +90,7 @@ final class SpaceSettingsModalCoordinatorBridgePresenter: NSObject { // MARK: - UIAdaptivePresentationControllerDelegate extension SpaceSettingsModalCoordinatorBridgePresenter: UIAdaptivePresentationControllerDelegate { - func roomNotificationSettingsCoordinatorDidComplete(_ presentationController: UIPresentationController) { - self.delegate?.spaceSettingsModalCoordinatorBridgePresenterDelegateDidCancel(self) + delegate?.spaceSettingsModalCoordinatorBridgePresenterDelegateDidCancel(self) } - } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinatorParameters.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinatorParameters.swift index 04778e3ed..85b3cf924 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinatorParameters.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinatorParameters.swift @@ -18,7 +18,6 @@ import Foundation /// SpaceSettingsModalCoordinator input parameters struct SpaceSettingsModalCoordinatorParameters { - /// The Matrix session let session: MXSession diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Coordinator/SpaceSettingsCoordinator.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Coordinator/SpaceSettingsCoordinator.swift index 74d88ad09..0224ae5cf 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Coordinator/SpaceSettingsCoordinator.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Coordinator/SpaceSettingsCoordinator.swift @@ -22,7 +22,6 @@ struct SpaceSettingsCoordinatorParameters { } final class SpaceSettingsCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -77,9 +76,9 @@ final class SpaceSettingsCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.spaceSettingsHostingController - + spaceSettingsHostingController } + // MARK: - Private private func pickImage(from sourceRect: CGRect) { @@ -90,6 +89,7 @@ final class SpaceSettingsCoordinator: Coordinator, Presentable { } // MARK: - SingleImagePickerPresenterDelegate + extension SpaceSettingsCoordinator: SingleImagePickerPresenterDelegate { func singleImagePickerPresenter(_ presenter: SingleImagePickerPresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) { spaceSettingsViewModel.updateAvatarImage(with: UIImage(data: imageData)) diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/MockSpaceSettingsScreenState.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/MockSpaceSettingsScreenState.swift index 3a28e7f65..f18482638 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/MockSpaceSettingsScreenState.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/MockSpaceSettingsScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,7 +38,7 @@ enum MockSpaceSettingsScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let service: MockSpaceSettingsService switch self { case .visibility(let visibility): @@ -53,7 +53,8 @@ enum MockSpaceSettingsScreenState: MockScreenState, CaseIterable { isNameEditable: true, isTopicEditable: true, isAddressEditable: true, - isAccessEditable: true) + isAccessEditable: true + ) service = MockSpaceSettingsService(roomProperties: roomProperties) case .notEditable: let roomProperties = SpaceSettingsRoomProperties( @@ -67,7 +68,8 @@ enum MockSpaceSettingsScreenState: MockScreenState, CaseIterable { isNameEditable: false, isTopicEditable: false, isAddressEditable: false, - isAccessEditable: false) + isAccessEditable: false + ) service = MockSpaceSettingsService(roomProperties: roomProperties) } let viewModel = SpaceSettingsViewModel.makeSpaceSettingsViewModel(service: service) diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/MatrixSDK/SpaceSettingsService.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/MatrixSDK/SpaceSettingsService.swift index 51028a11c..8b74bda67 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/MatrixSDK/SpaceSettingsService.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/MatrixSDK/SpaceSettingsService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,12 +14,11 @@ // limitations under the License. // -import Foundation import Combine +import Foundation import MatrixSDK class SpaceSettingsService: SpaceSettingsServiceProtocol { - // MARK: - Properties var userDefinedAddress: String? { @@ -36,6 +35,7 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol { updateRoomProperties() } } + private let room: MXRoom? private var roomEventListener: Any? @@ -88,12 +88,12 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol { init(session: MXSession, spaceId: String) { self.session = session self.spaceId = spaceId - self.room = session.room(withRoomId: spaceId) - self.isLoadingSubject = CurrentValueSubject(false) - self.showPostProcessAlert = CurrentValueSubject(false) - self.roomPropertiesSubject = CurrentValueSubject(self.roomProperties) - self.addressValidationSubject = CurrentValueSubject(.none("#")) - self.defaultAddress = "" + room = session.room(withRoomId: spaceId) + isLoadingSubject = CurrentValueSubject(false) + showPostProcessAlert = CurrentValueSubject(false) + roomPropertiesSubject = CurrentValueSubject(roomProperties) + addressValidationSubject = CurrentValueSubject(.none("#")) + defaultAddress = "" readRoomState() } @@ -121,15 +121,15 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol { private func readRoomState() { isLoadingSubject.send(true) - self.room?.state { [weak self] roomState in + room?.state { [weak self] roomState in self?.roomState = roomState self?.isLoadingSubject.send(false) } - roomEventListener = self.room?.listen(toEvents: { [weak self] event, direction, state in - self?.room?.state({ [weak self] roomState in + roomEventListener = room?.listen(toEvents: { [weak self] _, _, _ in + self?.room?.state { [weak self] roomState in self?.roomState = roomState - }) + } }) } @@ -164,7 +164,7 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol { return false } - let userPowerLevel = powerLevels.powerLevelOfUser(withUserID: self.session.myUserId) + let userPowerLevel = powerLevels.powerLevelOfUser(withUserID: session.myUserId) return userPowerLevel >= powerLevels.minimumPowerLevel(forNotifications: notification, defaultPower: powerLevels.stateDefault) } @@ -172,10 +172,10 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol { addressValidationOperation?.cancel() addressValidationOperation = nil - guard let userDefinedAddress = self.userDefinedAddress, !userDefinedAddress.isEmpty else { + guard let userDefinedAddress = userDefinedAddress, !userDefinedAddress.isEmpty else { let fullAddress = MXTools.fullLocalAlias(from: defaultAddress, with: session) - if let publicAddress = self.publicAddress, !publicAddress.isEmpty { + if let publicAddress = publicAddress, !publicAddress.isEmpty { addressValidationSubject.send(.current(fullAddress)) } else if defaultAddress.isEmpty { addressValidationSubject.send(.none(fullAddress)) @@ -191,8 +191,8 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol { private func validate(_ aliasLocalPart: String) { let fullAddress = MXTools.fullLocalAlias(from: aliasLocalPart, with: session) - if let publicAddress = self.publicAddress, publicAddress == aliasLocalPart { - self.addressValidationSubject.send(.current(fullAddress)) + if let publicAddress = publicAddress, publicAddress == aliasLocalPart { + addressValidationSubject.send(.current(fullAddress)) return } @@ -221,17 +221,17 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol { if let canonicalAlias = roomState.canonicalAlias { let localAliasPart = MXTools.extractLocalAliasPart(from: canonicalAlias) - self.publicAddress = localAliasPart - self.defaultAddress = localAliasPart + publicAddress = localAliasPart + defaultAddress = localAliasPart } else { - self.publicAddress = nil - self.defaultAddress = MXTools.validAliasLocalPart(from: roomState.name) + publicAddress = nil + defaultAddress = MXTools.validAliasLocalPart(from: roomState.name) } - self.roomProperties = SpaceSettingsRoomProperties( + roomProperties = SpaceSettingsRoomProperties( name: roomState.name, topic: roomState.topic, - address: self.defaultAddress, + address: defaultAddress, avatarUrl: roomState.avatar, visibility: visibility(with: roomState), allowedParentIds: allowedParentIds(with: roomState), @@ -239,12 +239,13 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol { isNameEditable: isField(ofType: kMXEventTypeStringRoomName, editableWith: roomState.powerLevels), isTopicEditable: isField(ofType: kMXEventTypeStringRoomTopic, editableWith: roomState.powerLevels), isAddressEditable: isField(ofType: kMXEventTypeStringRoomAliases, editableWith: roomState.powerLevels), - isAccessEditable: isField(ofType: kMXEventTypeStringRoomJoinRules, editableWith: roomState.powerLevels)) + isAccessEditable: isField(ofType: kMXEventTypeStringRoomJoinRules, editableWith: roomState.powerLevels) + ) } // MARK: - Post process - private var currentTaskIndex: Int = 0 + private var currentTaskIndex = 0 private var tasks: [PostProcessTask] = [] private var lastError: Error? private var completion: ((_ result: SpaceSettingsServiceCompletionResult) -> Void)? @@ -267,18 +268,18 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol { let type: PostProcessTaskType var state: PostProcessTaskState = .none var isFinished: Bool { - return state == .failure || state == .success + state == .failure || state == .success } static func == (lhs: PostProcessTask, rhs: PostProcessTask) -> Bool { - return lhs.type == rhs.type && lhs.state == rhs.state + lhs.type == rhs.type && lhs.state == rhs.state } } func update(roomName: String, topic: String, address: String, avatar: UIImage?, completion: ((_ result: SpaceSettingsServiceCompletionResult) -> Void)?) { // First attempt - if self.tasks.isEmpty { + if tasks.isEmpty { var tasks: [PostProcessTask] = [] if roomProperties?.name ?? "" != roomName { tasks.append(PostProcessTask(type: .updateName(roomName))) @@ -295,17 +296,17 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol { self.tasks = tasks } else { // Retry -> restart failed tasks - self.tasks = tasks.map({ task in + tasks = tasks.map { task in if task.state == .failure { return PostProcessTask(type: task.type, state: .none) } return task - }) + } } - self.isLoadingSubject.send(true) + isLoadingSubject.send(true) self.completion = completion - self.lastError = nil + lastError = nil currentTaskIndex = -1 runNextTask() } @@ -313,7 +314,7 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol { private func runNextTask() { currentTaskIndex += 1 guard currentTaskIndex < tasks.count else { - self.isLoadingSubject.send(false) + isLoadingSubject.send(false) if let error = lastError { showPostProcessAlert.send(true) completion?(.failure(error)) @@ -395,7 +396,7 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol { switch response { case .success: if let publicAddress = self.publicAddress { - self.currentOperation = self.room?.removeAlias(MXTools.fullLocalAlias(from: publicAddress, with: self.session), completion: { [weak self] response in + self.currentOperation = self.room?.removeAlias(MXTools.fullLocalAlias(from: publicAddress, with: self.session), completion: { [weak self] _ in guard let self = self else { return } self.setup(canonicalAlias: canonicalAlias) @@ -433,33 +434,33 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol { let avatarUp = MXKTools.forceImageOrientationUp(avatar) mediaUploader.uploadData(avatarUp?.jpegData(compressionQuality: 0.5), filename: nil, mimeType: "image/jpeg", - success: { [weak self] (urlString) in - guard let self = self else { return } + success: { [weak self] urlString in + guard let self = self else { return } - guard let urlString = urlString else { - self.updateCurrentTaskState(with: .failure) - self.runNextTask() - return - } - guard let url = URL(string: urlString) else { - self.updateCurrentTaskState(with: .failure) - self.runNextTask() - return - } + guard let urlString = urlString else { + self.updateCurrentTaskState(with: .failure) + self.runNextTask() + return + } + guard let url = URL(string: urlString) else { + self.updateCurrentTaskState(with: .failure) + self.runNextTask() + return + } - self.setAvatar(withURL: url) + self.setAvatar(withURL: url) }, - failure: { [weak self] (error) in - guard let self = self else { return } + failure: { [weak self] error in + guard let self = self else { return } - self.lastError = error - self.updateCurrentTaskState(with: .failure) - self.runNextTask() + self.lastError = error + self.updateCurrentTaskState(with: .failure) + self.runNextTask() }) } private func setAvatar(withURL url: URL) { - currentOperation = room?.setAvatar(url: url) { [weak self] (response) in + currentOperation = room?.setAvatar(url: url) { [weak self] response in guard let self = self else { return } switch response { @@ -473,5 +474,4 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol { self.runNextTask() } } - } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/Mock/MockSpaceSettingsService.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/Mock/MockSpaceSettingsService.swift index 96bc1f503..c9b7feb41 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/Mock/MockSpaceSettingsService.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/Mock/MockSpaceSettingsService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,11 +14,10 @@ // limitations under the License. // -import Foundation import Combine +import Foundation class MockSpaceSettingsService: SpaceSettingsServiceProtocol { - var spaceId: String var roomProperties: SpaceSettingsRoomProperties? private(set) var displayName: String? @@ -36,24 +35,19 @@ class MockSpaceSettingsService: SpaceSettingsServiceProtocol { self.spaceId = spaceId self.roomProperties = roomProperties self.displayName = displayName - self.isLoadingSubject = CurrentValueSubject(isLoading) + isLoadingSubject = CurrentValueSubject(isLoading) self.showPostProcessAlert = CurrentValueSubject(showPostProcessAlert) - self.roomPropertiesSubject = CurrentValueSubject(roomProperties) - self.addressValidationSubject = CurrentValueSubject(.none(spaceId)) + roomPropertiesSubject = CurrentValueSubject(roomProperties) + addressValidationSubject = CurrentValueSubject(.none(spaceId)) } - func update(roomName: String, topic: String, address: String, avatar: UIImage?, completion: ((SpaceSettingsServiceCompletionResult) -> Void)?) { - } + func update(roomName: String, topic: String, address: String, avatar: UIImage?, completion: ((SpaceSettingsServiceCompletionResult) -> Void)?) { } - func addressDidChange(_ newValue: String) { - - } + func addressDidChange(_ newValue: String) { } func simulateUpdate(addressValidationStatus: SpaceCreationSettingsAddressValidationStatus) { - self.addressValidationSubject.value = addressValidationStatus + addressValidationSubject.value = addressValidationStatus } - func trackSpace() { - - } + func trackSpace() { } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/SpaceSettingsServiceProtocol.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/SpaceSettingsServiceProtocol.swift index 4788c8e75..730a98bfe 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/SpaceSettingsServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/SpaceSettingsServiceProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation enum SpaceSettingsServiceCompletionResult { case success @@ -42,6 +42,7 @@ extension SpaceSettingsServiceProtocol { var mxContentUri: String? { roomProperties?.avatarUrl } + var matrixItemId: String { spaceId } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsModels.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsModels.swift index 6ca32eef4..2601d00d2 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsModels.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -81,15 +81,19 @@ struct SpaceSettingsViewState: BindableState { var isModified: Bool { userSelectedAvatar != nil || isRoomNameModified || isTopicModified || isAddressModified } + var isRoomNameModified: Bool { (roomProperties?.name ?? "") != bindings.name } + var isTopicModified: Bool { (roomProperties?.topic ?? "") != bindings.topic } + var isAddressModified: Bool { (roomProperties?.address ?? "") != bindings.address } + var bindings: SpaceSettingsViewModelBindings } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift index e98257b3f..b35fad927 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift @@ -14,14 +14,13 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias SpaceSettingsViewModelType = StateStoreViewModel + Never, + SpaceSettingsViewAction> class SpaceSettingsViewModel: SpaceSettingsViewModelType, SpaceSettingsViewModelProtocol { - // MARK: - Properties private static let options: [SpaceSettingsOption] = [ @@ -40,7 +39,7 @@ class SpaceSettingsViewModel: SpaceSettingsViewModelType, SpaceSettingsViewModel // MARK: - Setup static func makeSpaceSettingsViewModel(service: SpaceSettingsServiceProtocol) -> SpaceSettingsViewModelProtocol { - return SpaceSettingsViewModel(service: service) + SpaceSettingsViewModel(service: service) } private init(service: SpaceSettingsServiceProtocol) { @@ -54,7 +53,8 @@ class SpaceSettingsViewModel: SpaceSettingsViewModelType, SpaceSettingsViewModel name: service.roomProperties?.name ?? "", topic: service.roomProperties?.topic ?? "", address: service.roomProperties?.address ?? "", - showPostProcessAlert: service.showPostProcessAlert.value) + showPostProcessAlert: service.showPostProcessAlert.value + ) return SpaceSettingsViewState( defaultAddress: service.roomProperties?.address ?? "", @@ -68,12 +68,13 @@ class SpaceSettingsViewModel: SpaceSettingsViewModelType, SpaceSettingsViewModel isLoading: service.isLoadingSubject.value, visibilityString: (service.roomProperties?.visibility ?? .private).stringValue, options: options, - bindings: bindings) + bindings: bindings + ) } private func setupObservers() { service.isLoadingSubject.sink { [weak self] isLoading in - self?.state.isLoading = isLoading + self?.state.isLoading = isLoading } .store(in: &cancellables) diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModelProtocol.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModelProtocol.swift index a2859c8a4..808265e83 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol SpaceSettingsViewModelProtocol { - var completion: ((SpaceSettingsViewModelResult) -> Void)? { get set } static func makeSpaceSettingsViewModel(service: SpaceSettingsServiceProtocol) -> SpaceSettingsViewModelProtocol var context: SpaceSettingsViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Test/UI/SpaceSettingsUITests.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Test/UI/SpaceSettingsUITests.swift index abece45dd..583035c4c 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Test/UI/SpaceSettingsUITests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Test/UI/SpaceSettingsUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class SpaceSettingsUITests: MockScreenTestCase { // Tests to be implemented. diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Test/Unit/SpaceSettingsViewModelTests.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Test/Unit/SpaceSettingsViewModelTests.swift index 1e018fb85..102ba5669 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Test/Unit/SpaceSettingsViewModelTests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Test/Unit/SpaceSettingsViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI @@ -38,7 +38,8 @@ class SpaceSettingsViewModelTests: XCTestCase { isNameEditable: true, isTopicEditable: true, isAddressEditable: true, - isAccessEditable: true) + isAccessEditable: true + ) service = MockSpaceSettingsService(roomProperties: roomProperties, displayName: roomProperties.name, isLoading: false, showPostProcessAlert: false) viewModel = SpaceSettingsViewModel.makeSpaceSettingsViewModel(service: service) diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/View/SpaceSettings.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/View/SpaceSettings.swift index 42d8f71b3..8c1d31359 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/View/SpaceSettings.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/View/SpaceSettings.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct SpaceSettings: View { - // MARK: - Properties // MARK: Private @@ -32,7 +31,7 @@ struct SpaceSettings: View { ScrollView { VStack { avatarView - Spacer().frame(height:32) + Spacer().frame(height: 32) formView roomAccess options @@ -63,7 +62,7 @@ struct SpaceSettings: View { Alert(title: Text(VectorL10n.settingsTitle), message: Text(VectorL10n.spaceSettingsUpdateFailedMessage), primaryButton: .default(Text(VectorL10n.retry), action: { - updateSpace() + updateSpace() }), secondaryButton: .cancel()) }) @@ -80,7 +79,7 @@ struct SpaceSettings: View { GeometryReader { reader in ZStack { SpaceAvatarImage(mxContentUri: viewModel.viewState.avatar.mxContentUri, matrixItemId: viewModel.viewState.avatar.matrixItemId, displayName: viewModel.viewState.avatar.displayName, size: .xxLarge) - .padding(6) + .padding(6) if let image = viewModel.viewState.userSelectedAvatar { Image(uiImage: image) .resizable() @@ -89,13 +88,13 @@ struct SpaceSettings: View { .clipShape(RoundedRectangle(cornerRadius: 8)) } }.padding(10) - .onTapGesture { - guard viewModel.viewState.roomProperties?.isAvatarEditable == true else { - return + .onTapGesture { + guard viewModel.viewState.roomProperties?.isAvatarEditable == true else { + return + } + ResponderManager.resignFirstResponder() + viewModel.send(viewAction: .pickImage(reader.frame(in: .global))) } - ResponderManager.resignFirstResponder() - viewModel.send(viewAction: .pickImage(reader.frame(in: .global))) - } } if viewModel.viewState.roomProperties?.isAvatarEditable == true { Image(uiImage: Asset.Images.spaceCreationCamera.image) @@ -110,26 +109,28 @@ struct SpaceSettings: View { @ViewBuilder private var formView: some View { - VStack{ + VStack { RoundedBorderTextField( title: VectorL10n.createRoomPlaceholderName, placeHolder: "", text: $viewModel.name, footerText: viewModel.viewState.roomNameError, isError: true, - configuration: UIKitTextInputConfiguration( returnKeyType: .next)) - .padding(.horizontal, 2) - .padding(.bottom, 20) - .disabled(viewModel.viewState.roomProperties?.isNameEditable != true) + configuration: UIKitTextInputConfiguration(returnKeyType: .next) + ) + .padding(.horizontal, 2) + .padding(.bottom, 20) + .disabled(viewModel.viewState.roomProperties?.isNameEditable != true) RoundedBorderTextEditor( title: VectorL10n.spaceTopic, placeHolder: VectorL10n.spaceTopic, text: $viewModel.topic, textMaxHeight: 72, - error: nil) - .padding(.horizontal, 2) - .padding(.bottom, viewModel.viewState.showRoomAddress ? 20 : 3) - .disabled(viewModel.viewState.roomProperties?.isTopicEditable != true) + error: nil + ) + .padding(.horizontal, 2) + .padding(.bottom, viewModel.viewState.showRoomAddress ? 20 : 3) + .disabled(viewModel.viewState.roomProperties?.isTopicEditable != true) if viewModel.viewState.showRoomAddress { RoundedBorderTextField( title: VectorL10n.spacesCreationAddress, @@ -137,14 +138,15 @@ struct SpaceSettings: View { text: $viewModel.address, footerText: viewModel.viewState.addressMessage, isError: !viewModel.viewState.isAddressValid, - configuration: UIKitTextInputConfiguration(keyboardType: .URL, returnKeyType: .done, autocapitalizationType: .none), onTextChanged: { + configuration: UIKitTextInputConfiguration(keyboardType: .URL, returnKeyType: .done, autocapitalizationType: .none), onTextChanged: { newText in viewModel.send(viewAction: .addressChanged(newText)) - }) - .disabled(viewModel.viewState.roomProperties?.isAddressEditable != true) - .padding(.horizontal, 2) - .padding(.bottom, 3) - .accessibility(identifier: "addressTextField") + } + ) + .disabled(viewModel.viewState.roomProperties?.isAddressEditable != true) + .padding(.horizontal, 2) + .padding(.bottom, 3) + .accessibility(identifier: "addressTextField") } } .padding(.horizontal) @@ -153,7 +155,7 @@ struct SpaceSettings: View { @ViewBuilder private var roomAccess: some View { VStack(alignment: .leading) { - Spacer().frame(height:24) + Spacer().frame(height: 24) Text(VectorL10n.spaceSettingsAccessSection) .font(theme.fonts.footnote) .foregroundColor(theme.colors.secondaryContent) @@ -161,11 +163,12 @@ struct SpaceSettings: View { .padding(.bottom, 4) SpaceSettingsOptionListItem( title: VectorL10n.roomDetailsAccessRowTitle, - value: viewModel.viewState.visibilityString) { + value: viewModel.viewState.visibilityString + ) { ResponderManager.resignFirstResponder() viewModel.send(viewAction: .optionSelected(.visibility)) } - .disabled(viewModel.viewState.roomProperties?.isAccessEditable != true) + .disabled(viewModel.viewState.roomProperties?.isAccessEditable != true) } } @@ -182,11 +185,12 @@ struct SpaceSettings: View { SpaceSettingsOptionListItem( icon: option.icon, title: option.title, - value: option.value) { + value: option.value + ) { ResponderManager.resignFirstResponder() viewModel.send(viewAction: .optionSelected(option.id)) } - .disabled(!option.isEnabled) + .disabled(!option.isEnabled) } } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/View/SpaceSettingsOptionListItem.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/View/SpaceSettingsOptionListItem.swift index 6287ab24d..11b921ce7 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/View/SpaceSettingsOptionListItem.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/View/SpaceSettingsOptionListItem.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct SpaceSettingsOptionListItem: View { - // MARK: Private @Environment(\.theme) private var theme: ThemeSwiftUI @@ -85,7 +84,6 @@ struct SpaceSettingsOptionListItem: View { // MARK: - Previews struct SpaceSettingsOptionListItem_Previews: PreviewProvider { - static var previews: some View { sampleView.theme(.light).preferredColorScheme(.light) sampleView.theme(.dark).preferredColorScheme(.dark) @@ -100,5 +98,4 @@ struct SpaceSettingsOptionListItem_Previews: PreviewProvider { .disabled(true) } } - } diff --git a/RiotSwiftUI/Modules/Template/SimpleScreenExample/Coordinator/TemplateSimpleScreenCoordinator.swift b/RiotSwiftUI/Modules/Template/SimpleScreenExample/Coordinator/TemplateSimpleScreenCoordinator.swift index a794c88ab..ec2bd32d2 100644 --- a/RiotSwiftUI/Modules/Template/SimpleScreenExample/Coordinator/TemplateSimpleScreenCoordinator.swift +++ b/RiotSwiftUI/Modules/Template/SimpleScreenExample/Coordinator/TemplateSimpleScreenCoordinator.swift @@ -14,15 +14,14 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI struct TemplateSimpleScreenCoordinatorParameters { let promptType: TemplateSimpleScreenPromptType } final class TemplateSimpleScreenCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -65,7 +64,7 @@ final class TemplateSimpleScreenCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.templateSimpleScreenHostingController + templateSimpleScreenHostingController } // MARK: - Private diff --git a/RiotSwiftUI/Modules/Template/SimpleScreenExample/MockTemplateSimpleScreenScreenState.swift b/RiotSwiftUI/Modules/Template/SimpleScreenExample/MockTemplateSimpleScreenScreenState.swift index 4a2d885cd..5db4451ec 100644 --- a/RiotSwiftUI/Modules/Template/SimpleScreenExample/MockTemplateSimpleScreenScreenState.swift +++ b/RiotSwiftUI/Modules/Template/SimpleScreenExample/MockTemplateSimpleScreenScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,7 +37,7 @@ enum MockTemplateSimpleScreenScreenState: MockScreenState, CaseIterable { } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let promptType: TemplateSimpleScreenPromptType switch self { case .promptType(let type): diff --git a/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenModels.swift b/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenModels.swift index 2238fc709..d1755f432 100644 --- a/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenModels.swift +++ b/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenViewModel.swift b/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenViewModel.swift index 892e91a82..c90e70346 100644 --- a/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenViewModel.swift +++ b/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenViewModel.swift @@ -17,11 +17,10 @@ import SwiftUI typealias TemplateSimpleScreenViewModelType = StateStoreViewModel + Never, + TemplateSimpleScreenViewAction> class TemplateSimpleScreenViewModel: TemplateSimpleScreenViewModelType, TemplateSimpleScreenViewModelProtocol { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenViewModelProtocol.swift b/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenViewModelProtocol.swift index 9d19bf528..cbab2a69a 100644 --- a/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol TemplateSimpleScreenViewModelProtocol { - var completion: ((TemplateSimpleScreenViewModelResult) -> Void)? { get set } var context: TemplateSimpleScreenViewModelType.Context { get } } diff --git a/RiotSwiftUI/Modules/Template/SimpleScreenExample/Test/UI/TemplateSimpleScreenUITests.swift b/RiotSwiftUI/Modules/Template/SimpleScreenExample/Test/UI/TemplateSimpleScreenUITests.swift index 4b02918dd..4af3689ac 100644 --- a/RiotSwiftUI/Modules/Template/SimpleScreenExample/Test/UI/TemplateSimpleScreenUITests.swift +++ b/RiotSwiftUI/Modules/Template/SimpleScreenExample/Test/UI/TemplateSimpleScreenUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class TemplateSimpleScreenUITests: MockScreenTestCase { func testTemplateSimpleScreenPromptRegular() { diff --git a/RiotSwiftUI/Modules/Template/SimpleScreenExample/Test/Unit/TemplateSimpleScreenViewModelTests.swift b/RiotSwiftUI/Modules/Template/SimpleScreenExample/Test/Unit/TemplateSimpleScreenViewModelTests.swift index c3c0849a4..2ae36f733 100644 --- a/RiotSwiftUI/Modules/Template/SimpleScreenExample/Test/Unit/TemplateSimpleScreenViewModelTests.swift +++ b/RiotSwiftUI/Modules/Template/SimpleScreenExample/Test/Unit/TemplateSimpleScreenViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Template/SimpleScreenExample/View/TemplateSimpleScreen.swift b/RiotSwiftUI/Modules/Template/SimpleScreenExample/View/TemplateSimpleScreen.swift index fa43120b6..4fe4302ef 100644 --- a/RiotSwiftUI/Modules/Template/SimpleScreenExample/View/TemplateSimpleScreen.swift +++ b/RiotSwiftUI/Modules/Template/SimpleScreenExample/View/TemplateSimpleScreen.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct TemplateSimpleScreen: View { - // MARK: - Properties // MARK: Private @@ -64,10 +63,10 @@ struct TemplateSimpleScreen: View { Image(viewModel.viewState.promptType.image.name) .resizable() .scaledToFit() - .frame(width:100) + .frame(width: 100) .foregroundColor(theme.colors.accent) - HStack{ + HStack { Text("Counter: \(viewModel.viewState.count)") .foregroundColor(theme.colors.primaryContent) diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Coordinator/TemplateUserProfileCoordinator.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Coordinator/TemplateUserProfileCoordinator.swift index c2050e6ed..38525cc2c 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Coordinator/TemplateUserProfileCoordinator.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Coordinator/TemplateUserProfileCoordinator.swift @@ -14,15 +14,14 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI struct TemplateUserProfileCoordinatorParameters { let session: MXSession } final class TemplateUserProfileCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -68,7 +67,7 @@ final class TemplateUserProfileCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return self.templateUserProfileHostingController + templateUserProfileHostingController } // MARK: - Private diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/MockTemplateUserProfileScreenState.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/MockTemplateUserProfileScreenState.swift index f43fb2557..08082aec5 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/MockTemplateUserProfileScreenState.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/MockTemplateUserProfileScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,12 +35,12 @@ enum MockTemplateUserProfileScreenState: MockScreenState, CaseIterable { static var allCases: [MockTemplateUserProfileScreenState] { // Each of the presence statuses TemplateUserProfilePresence.allCases.map(MockTemplateUserProfileScreenState.presence) - // A long display name - + [.longDisplayName("Somebody with a super long name we would like to test")] + // A long display name + + [.longDisplayName("Somebody with a super long name we would like to test")] } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let service: MockTemplateUserProfileService switch self { case .presence(let presence): diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Service/MatrixSDK/TemplateUserProfileService.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Service/MatrixSDK/TemplateUserProfileService.swift index 77341c1b3..409e868a5 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Service/MatrixSDK/TemplateUserProfileService.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Service/MatrixSDK/TemplateUserProfileService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,11 +14,10 @@ // limitations under the License. // -import Foundation import Combine +import Foundation class TemplateUserProfileService: TemplateUserProfileServiceProtocol { - // MARK: - Properties // MARK: Private @@ -46,8 +45,8 @@ class TemplateUserProfileService: TemplateUserProfileServiceProtocol { init(session: MXSession) { self.session = session - self.presenceSubject = CurrentValueSubject(TemplateUserProfilePresence(mxPresence: session.myUser.presence)) - self.listenerReference = setupPresenceListener() + presenceSubject = CurrentValueSubject(TemplateUserProfilePresence(mxPresence: session.myUser.presence)) + listenerReference = setupPresenceListener() } deinit { @@ -70,8 +69,7 @@ class TemplateUserProfileService: TemplateUserProfileServiceProtocol { } } -fileprivate extension TemplateUserProfilePresence { - +private extension TemplateUserProfilePresence { init(mxPresence: MXPresence) { switch mxPresence { case .online: diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Service/Mock/MockTemplateUserProfileService.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Service/Mock/MockTemplateUserProfileService.swift index 86fe1c49a..4bd94dbd4 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Service/Mock/MockTemplateUserProfileService.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Service/Mock/MockTemplateUserProfileService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation class MockTemplateUserProfileService: TemplateUserProfileServiceProtocol { var presenceSubject: CurrentValueSubject @@ -23,19 +23,17 @@ class MockTemplateUserProfileService: TemplateUserProfileServiceProtocol { let userId: String let displayName: String? let avatarUrl: String? - init( - userId: String = "@alice:matrix.org", - displayName: String? = "Alice", - avatarUrl: String? = "mxc://matrix.org/VyNYAgahaiAzUoOeZETtQ", - presence: TemplateUserProfilePresence = .offline - ) { + init(userId: String = "@alice:matrix.org", + displayName: String? = "Alice", + avatarUrl: String? = "mxc://matrix.org/VyNYAgahaiAzUoOeZETtQ", + presence: TemplateUserProfilePresence = .offline) { self.userId = userId self.displayName = displayName self.avatarUrl = avatarUrl - self.presenceSubject = CurrentValueSubject(presence) + presenceSubject = CurrentValueSubject(presence) } func simulateUpdate(presence: TemplateUserProfilePresence) { - self.presenceSubject.value = presence + presenceSubject.value = presence } } diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Service/TemplateUserProfileServiceProtocol.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Service/TemplateUserProfileServiceProtocol.swift index a446d840f..d4317d2e3 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Service/TemplateUserProfileServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Service/TemplateUserProfileServiceProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation protocol TemplateUserProfileServiceProtocol: Avatarable { var userId: String { get } @@ -30,6 +30,7 @@ extension TemplateUserProfileServiceProtocol { var mxContentUri: String? { avatarUrl } + var matrixItemId: String { userId } diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileModels.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileModels.swift index e57ce601e..6456e2b08 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileModels.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileViewModel.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileViewModel.swift index 3dfc27a9f..554eac157 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileViewModel.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileViewModel.swift @@ -14,15 +14,14 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias TemplateUserProfileViewModelType = StateStoreViewModel + Never, + TemplateUserProfileViewAction> class TemplateUserProfileViewModel: TemplateUserProfileViewModelType, TemplateUserProfileViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -36,7 +35,7 @@ class TemplateUserProfileViewModel: TemplateUserProfileViewModelType, TemplateUs // MARK: - Setup static func makeTemplateUserProfileViewModel(templateUserProfileService: TemplateUserProfileServiceProtocol) -> TemplateUserProfileViewModelProtocol { - return TemplateUserProfileViewModel(templateUserProfileService: templateUserProfileService) + TemplateUserProfileViewModel(templateUserProfileService: templateUserProfileService) } private init(templateUserProfileService: TemplateUserProfileServiceProtocol) { @@ -46,7 +45,7 @@ class TemplateUserProfileViewModel: TemplateUserProfileViewModelType, TemplateUs } private static func defaultState(templateUserProfileService: TemplateUserProfileServiceProtocol) -> TemplateUserProfileViewState { - return TemplateUserProfileViewState( + TemplateUserProfileViewState( avatar: templateUserProfileService.avatarData, displayName: templateUserProfileService.displayName, presence: templateUserProfileService.presenceSubject.value, diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileViewModelProtocol.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileViewModelProtocol.swift index 6862c6a20..6df29b71d 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation protocol TemplateUserProfileViewModelProtocol { - var completion: ((TemplateUserProfileViewModelResult) -> Void)? { get set } static func makeTemplateUserProfileViewModel(templateUserProfileService: TemplateUserProfileServiceProtocol) -> TemplateUserProfileViewModelProtocol var context: TemplateUserProfileViewModelType.Context { get } diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Test/UI/TemplateUserProfileUITests.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Test/UI/TemplateUserProfileUITests.swift index b23b4174e..feecd0e37 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Test/UI/TemplateUserProfileUITests.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Test/UI/TemplateUserProfileUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class TemplateUserProfileUITests: MockScreenTestCase { func testTemplateUserProfilePresenceIdle() { @@ -53,5 +53,4 @@ class TemplateUserProfileUITests: MockScreenTestCase { XCTAssert(displayNameText.exists) XCTAssertEqual(displayNameText.label, name) } - } diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Test/Unit/TemplateUserProfileViewModelTests.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Test/Unit/TemplateUserProfileViewModelTests.swift index be0420f8f..c3fffa429 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Test/Unit/TemplateUserProfileViewModelTests.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Test/Unit/TemplateUserProfileViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI @@ -24,6 +24,7 @@ class TemplateUserProfileViewModelTests: XCTestCase { static let presenceInitialValue: TemplateUserProfilePresence = .offline static let displayName = "Alice" } + var service: MockTemplateUserProfileService! var viewModel: TemplateUserProfileViewModelProtocol! var context: TemplateUserProfileViewModelType.Context! diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfile.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfile.swift index 073431f39..7bf54cd21 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfile.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfile.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct TemplateUserProfile: View { - // MARK: - Properties // MARK: Private @@ -36,7 +35,7 @@ struct TemplateUserProfile: View { presence: viewModel.viewState.presence ) Divider() - HStack{ + HStack { Text("Counter: \(viewModel.viewState.count)") .font(theme.fonts.title2) .foregroundColor(theme.colors.secondaryContent) diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfileHeader.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfileHeader.swift index b67f6971b..e3ec295e2 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfileHeader.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfileHeader.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,13 +17,14 @@ import SwiftUI struct TemplateUserProfileHeader: View { - // MARK: - Properties // MARK: Private + @Environment(\.theme) private var theme: ThemeSwiftUI // MARK: Public + let avatar: AvatarInputProtocol? let displayName: String? let presence: TemplateUserProfilePresence @@ -32,9 +33,9 @@ struct TemplateUserProfileHeader: View { VStack { if let avatar = avatar { AvatarImage(avatarData: avatar, size: .xxLarge) - .padding(.vertical) + .padding(.vertical) } - VStack(spacing: 8){ + VStack(spacing: 8) { Text(displayName ?? "") .font(theme.fonts.title3) .accessibility(identifier: "displayNameText") diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfilePresenceView.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfilePresenceView.swift index 0428474cf..1a49a4d18 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfilePresenceView.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfilePresenceView.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,10 +17,10 @@ import SwiftUI struct TemplateUserProfilePresenceView: View { - // MARK: - Properties // MARK: Public + let presence: TemplateUserProfilePresence var body: some View { @@ -54,7 +54,7 @@ struct TemplateUserProfilePresenceView: View { struct TemplateUserProfilePresenceView_Previews: PreviewProvider { static var previews: some View { - VStack(alignment:.leading){ + VStack(alignment: .leading) { Text("Presence") ForEach(TemplateUserProfilePresence.allCases) { presence in TemplateUserProfilePresenceView(presence: presence) diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinator.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinator.swift index fa6029248..869766600 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinator.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinator.swift @@ -18,7 +18,6 @@ import UIKit @objcMembers final class TemplateRoomsCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -26,7 +25,7 @@ final class TemplateRoomsCoordinator: Coordinator, Presentable { private let parameters: TemplateRoomsCoordinatorParameters private var navigationRouter: NavigationRouterType { - return self.parameters.navigationRouter + parameters.navigationRouter } // MARK: Public @@ -40,37 +39,36 @@ final class TemplateRoomsCoordinator: Coordinator, Presentable { init(parameters: TemplateRoomsCoordinatorParameters) { self.parameters = parameters - } + } // MARK: - Public - func start() { MXLog.debug("[TemplateRoomsCoordinator] did start.") - let rootCoordinator = self.createTemplateRoomListCoordinator() + let rootCoordinator = createTemplateRoomListCoordinator() rootCoordinator.start() - self.add(childCoordinator: rootCoordinator) + add(childCoordinator: rootCoordinator) - if self.navigationRouter.modules.isEmpty == false { - self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in + if navigationRouter.modules.isEmpty == false { + navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in self?.remove(childCoordinator: rootCoordinator) }) } else { - self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in + navigationRouter.setRootModule(rootCoordinator) { [weak self] in self?.remove(childCoordinator: rootCoordinator) } } } func toPresentable() -> UIViewController { - return self.navigationRouter.toPresentable() + navigationRouter.toPresentable() } // MARK: - Private private func createTemplateRoomListCoordinator() -> TemplateRoomListCoordinator { - let coordinator: TemplateRoomListCoordinator = TemplateRoomListCoordinator(parameters: TemplateRoomListCoordinatorParameters(session: parameters.session)) + let coordinator = TemplateRoomListCoordinator(parameters: TemplateRoomListCoordinatorParameters(session: parameters.session)) coordinator.callback = { [weak self] result in MXLog.debug("[TemplateRoomsCoordinator] TemplateRoomListCoordinator did complete with result \(result).") @@ -86,7 +84,7 @@ final class TemplateRoomsCoordinator: Coordinator, Presentable { } private func createTemplateRoomChatCoordinator(room: MXRoom) -> TemplateRoomChatCoordinator { - let coordinator: TemplateRoomChatCoordinator = TemplateRoomChatCoordinator(parameters: TemplateRoomChatCoordinatorParameters(room: room)) + let coordinator = TemplateRoomChatCoordinator(parameters: TemplateRoomChatCoordinatorParameters(room: room)) return coordinator } @@ -99,7 +97,7 @@ final class TemplateRoomsCoordinator: Coordinator, Presentable { add(childCoordinator: templateRoomChatCoordinator) - self.navigationRouter.push(templateRoomChatCoordinator, animated: true, popCompletion: { [weak self] in + navigationRouter.push(templateRoomChatCoordinator, animated: true, popCompletion: { [weak self] in self?.remove(childCoordinator: templateRoomChatCoordinator) }) diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinatorParameters.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinatorParameters.swift index dd9cd4295..bce458e79 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinatorParameters.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinatorParameters.swift @@ -18,7 +18,6 @@ import Foundation /// TemplateRoomsCoordinator input parameters struct TemplateRoomsCoordinatorParameters { - /// The Matrix session let session: MXSession diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Coordinator/TemplateRoomChatCoordinator.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Coordinator/TemplateRoomChatCoordinator.swift index 5f1dcd5e6..2a8c79c7e 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Coordinator/TemplateRoomChatCoordinator.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Coordinator/TemplateRoomChatCoordinator.swift @@ -21,7 +21,6 @@ struct TemplateRoomChatCoordinatorParameters { } final class TemplateRoomChatCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -48,6 +47,7 @@ final class TemplateRoomChatCoordinator: Coordinator, Presentable { } // MARK: - Public + func start() { MXLog.debug("[TemplateRoomChatCoordinator] did start.") templateRoomChatViewModel.callback = { [weak self] result in @@ -56,12 +56,11 @@ final class TemplateRoomChatCoordinator: Coordinator, Presentable { switch result { case .done: self.callback?() - break } } } func toPresentable() -> UIViewController { - return self.templateRoomChatHostingController + templateRoomChatHostingController } } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/MockTemplateRoomChatScreenState.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/MockTemplateRoomChatScreenState.swift index b755cc2fd..8ec8bced0 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/MockTemplateRoomChatScreenState.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/MockTemplateRoomChatScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -56,7 +56,7 @@ enum MockTemplateRoomChatScreenState: MockScreenState, CaseIterable { return ( [service, viewModel], AnyView(TemplateRoomChat(viewModel: viewModel.context) - .addDependency(MockAvatarService.example)) + .addDependency(MockAvatarService.example)) ) } } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift index 6019c2535..1428b6f70 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,11 +14,10 @@ // limitations under the License. // -import Foundation import Combine +import Foundation class TemplateRoomChatService: TemplateRoomChatServiceProtocol { - // MARK: - Properties // MARK: Private @@ -29,22 +28,23 @@ class TemplateRoomChatService: TemplateRoomChatServiceProtocol { private var eventBatch: [MXEvent] private var roomListenerReference: Any? - // MARK: Public + private(set) var chatMessagesSubject: CurrentValueSubject<[TemplateRoomChatMessage], Never> private(set) var roomInitializationStatus: CurrentValueSubject var roomName: String? { - self.room.summary.displayname + room.summary.displayname } + // MARK: - Setup init(room: MXRoom) { self.room = room - self.eventFormatter = EventFormatter(matrixSession: room.mxSession) - self.chatMessagesSubject = CurrentValueSubject([]) - self.roomInitializationStatus = CurrentValueSubject(.notInitialized) - self.eventBatch = [MXEvent]() + eventFormatter = EventFormatter(matrixSession: room.mxSession) + chatMessagesSubject = CurrentValueSubject([]) + roomInitializationStatus = CurrentValueSubject(.notInitialized) + eventBatch = [MXEvent]() initializeRoom() } @@ -54,14 +54,15 @@ class TemplateRoomChatService: TemplateRoomChatServiceProtocol { } // MARK: Public + func send(textMessage: String) { - var localEcho: MXEvent? = nil + var localEcho: MXEvent? room.sendTextMessage(textMessage, threadId: nil, localEcho: &localEcho, completion: { _ in }) } // MARK: Private - private func initializeRoom(){ + private func initializeRoom() { room.liveTimeline { [weak self] timeline in guard let self = self, let timeline = timeline @@ -70,21 +71,20 @@ class TemplateRoomChatService: TemplateRoomChatServiceProtocol { } self.timeline = timeline timeline.resetPagination() - self.roomListenerReference = timeline.listenToEvents([.roomMessage], { [weak self] event, direction, roomState in + self.roomListenerReference = timeline.listenToEvents([.roomMessage]) { [weak self] event, direction, _ in guard let self = self else { return } if direction == .backwards { self.eventBatch.append(event) } else { self.chatMessagesSubject.value += self.mapChatMessages(from: [event]) } - - }) + } timeline.paginate(200, direction: .backwards, onlyFromStore: false) { result in guard result.isSuccess else { self.roomInitializationStatus.value = .failedToInitialize return } - let sortedBatch = self.eventBatch.sorted(by: { $0.originServerTs < $1.originServerTs}) + let sortedBatch = self.eventBatch.sorted(by: { $0.originServerTs < $1.originServerTs }) self.chatMessagesSubject.value = self.mapChatMessages(from: sortedBatch) self.roomInitializationStatus.value = .initialized } @@ -92,15 +92,14 @@ class TemplateRoomChatService: TemplateRoomChatServiceProtocol { } private func mapChatMessages(from events: [MXEvent]) -> [TemplateRoomChatMessage] { - return events - .filter({ event in + events + .filter { event in event.type == kMXEventTypeStringRoomMessage && event.content[kMXMessageTypeKey] as? String == kMXMessageTypeText // TODO: New to our SwiftUI Template? Why not implement another message type like image? - - }) - .compactMap({ event -> TemplateRoomChatMessage? in + } + .compactMap { event -> TemplateRoomChatMessage? in guard let eventId = event.eventId, let body = event.content[kMXMessageBodyKey] as? String, let sender = senderForMessage(event: event) @@ -112,7 +111,7 @@ class TemplateRoomChatService: TemplateRoomChatServiceProtocol { sender: sender, timestamp: Date(timeIntervalSince1970: TimeInterval(event.originServerTs / 1000)) ) - }) + } } private func senderForMessage(event: MXEvent) -> TemplateRoomChatMember? { diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/Mock/MockTemplateRoomChatService.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/Mock/MockTemplateRoomChatService.swift index d4da0a63e..c920b2a8f 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/Mock/MockTemplateRoomChatService.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/Mock/MockTemplateRoomChatService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,39 +14,38 @@ // limitations under the License. // -import Foundation import Combine +import Foundation class MockTemplateRoomChatService: TemplateRoomChatServiceProtocol { - let roomName: String? = "New Vector" static let amadine = TemplateRoomChatMember(id: "@amadine:matrix.org", avatarUrl: "!aaabaa:matrix.org", displayName: "Amadine") static let mathew = TemplateRoomChatMember(id: "@mathew:matrix.org", avatarUrl: "!bbabb:matrix.org", displayName: "Mathew") static let mockMessages = [ - TemplateRoomChatMessage(id: "!0:matrix.org", content: .text(TemplateRoomChatMessageTextContent(body: "Shall I put it live?")) , sender: amadine, timestamp: Date(timeIntervalSinceNow: 60 * -3)), + TemplateRoomChatMessage(id: "!0:matrix.org", content: .text(TemplateRoomChatMessageTextContent(body: "Shall I put it live?")), sender: amadine, timestamp: Date(timeIntervalSinceNow: 60 * -3)), TemplateRoomChatMessage(id: "!1:matrix.org", content: .text(TemplateRoomChatMessageTextContent(body: "Yea go for it! ...and then let's head to the pub")), sender: mathew, timestamp: Date(timeIntervalSinceNow: 60)), TemplateRoomChatMessage(id: "!2:matrix.org", content: .text(TemplateRoomChatMessageTextContent(body: "Deal.")), sender: amadine, timestamp: Date(timeIntervalSinceNow: 60 * -2)), - TemplateRoomChatMessage(id: "!3:matrix.org", content: .text(TemplateRoomChatMessageTextContent(body: "Ok, Done. 🍻")), sender: amadine, timestamp: Date(timeIntervalSinceNow: 60 * -1)), + TemplateRoomChatMessage(id: "!3:matrix.org", content: .text(TemplateRoomChatMessageTextContent(body: "Ok, Done. 🍻")), sender: amadine, timestamp: Date(timeIntervalSinceNow: 60 * -1)) ] var roomInitializationStatus: CurrentValueSubject var chatMessagesSubject: CurrentValueSubject<[TemplateRoomChatMessage], Never> init(messages: [TemplateRoomChatMessage] = mockMessages) { - self.roomInitializationStatus = CurrentValueSubject(.notInitialized) - self.chatMessagesSubject = CurrentValueSubject(messages) + roomInitializationStatus = CurrentValueSubject(.notInitialized) + chatMessagesSubject = CurrentValueSubject(messages) } func send(textMessage: String) { let newMessage = TemplateRoomChatMessage(id: "!\(chatMessagesSubject.value.count):matrix.org", content: .text(TemplateRoomChatMessageTextContent(body: textMessage)), sender: Self.amadine, timestamp: Date()) - self.chatMessagesSubject.value += [newMessage] + chatMessagesSubject.value += [newMessage] } func simulateUpdate(initializationStatus: TemplateRoomChatRoomInitializationStatus) { - self.roomInitializationStatus.value = initializationStatus + roomInitializationStatus.value = initializationStatus } func simulateUpdate(messages: [TemplateRoomChatMessage]) { - self.chatMessagesSubject.value = messages + chatMessagesSubject.value = messages } } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/TemplateRoomChatServiceProtocol.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/TemplateRoomChatServiceProtocol.swift index 012c5ab85..a14ee05e3 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/TemplateRoomChatServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/TemplateRoomChatServiceProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation protocol TemplateRoomChatServiceProtocol { var roomInitializationStatus: CurrentValueSubject { get } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatModels.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatModels.swift index e9aa09141..84f09a068 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatModels.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -88,7 +88,6 @@ enum TemplateRoomChatViewModelAction { case done } - // MARK: - View /// Actions send from the `View` to the `ViewModel`. diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatViewModel.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatViewModel.swift index 609d6a1f6..f0a3ecf3a 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatViewModel.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatViewModel.swift @@ -14,18 +14,18 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias TemplateRoomChatViewModelType = StateStoreViewModel + Never, + TemplateRoomChatViewAction> class TemplateRoomChatViewModel: TemplateRoomChatViewModelType, TemplateRoomChatViewModelProtocol { - enum Constants { - static let maxTimeBeforeNewBubble: TimeInterval = 5*60 + static let maxTimeBeforeNewBubble: TimeInterval = 5 * 60 } + // MARK: - Properties // MARK: Private @@ -70,9 +70,8 @@ class TemplateRoomChatViewModel: TemplateRoomChatViewModelType, TemplateRoomChat } private static func makeBubbles(messages: [TemplateRoomChatMessage]) -> [TemplateRoomChatBubble] { - var bubbleOrder = [String]() - var bubbleMap = [String:TemplateRoomChatBubble]() + var bubbleMap = [String: TemplateRoomChatBubble]() messages.enumerated().forEach { i, message in // New message content @@ -85,9 +84,8 @@ class TemplateRoomChatViewModel: TemplateRoomChatViewModelType, TemplateRoomChat let lastBubbleId = bubbleOrder.last, var lastBubble = bubbleMap[lastBubbleId], lastBubble.sender.id == message.sender.id, - let interveningTime = lastBubble.items.last?.timestamp.timeIntervalSince(message.timestamp), - abs(interveningTime) < Constants.maxTimeBeforeNewBubble - { + let interveningTime = lastBubble.items.last?.timestamp.timeIntervalSince(message.timestamp), + abs(interveningTime) < Constants.maxTimeBeforeNewBubble { // if the last bubble's last message was within // the last 5 minutes append let item = TemplateRoomChatBubbleItem( @@ -108,7 +106,7 @@ class TemplateRoomChatViewModel: TemplateRoomChatViewModelType, TemplateRoomChat bubbleMap[bubble.id] = bubble } } - return bubbleOrder.compactMap({ bubbleMap[$0] }) + return bubbleOrder.compactMap { bubbleMap[$0] } } // MARK: - Public diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatViewModelProtocol.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatViewModelProtocol.swift index 3dd0ad937..4c2799351 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Test/UI/TemplateRoomChatUITests.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Test/UI/TemplateRoomChatUITests.swift index 449eddcc0..cf764cb2d 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Test/UI/TemplateRoomChatUITests.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Test/UI/TemplateRoomChatUITests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class TemplateRoomChatUITests: MockScreenTestCase { func testInitializingRoom() { @@ -44,12 +44,11 @@ class TemplateRoomChatUITests: MockScreenTestCase { // Verify bubble grouping with: // 3 bubbles - let bubbleCount = app.images.matching(identifier:"bubbleImage").count + let bubbleCount = app.images.matching(identifier: "bubbleImage").count XCTAssertEqual(bubbleCount, 3) // and 4 text items - let bubbleTextItemCount = app.staticTexts.matching(identifier:"bubbleTextContent").count + let bubbleTextItemCount = app.staticTexts.matching(identifier: "bubbleTextContent").count XCTAssertEqual(bubbleTextItemCount, 4) } - } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Test/Unit/TemplateRoomChatViewModelTests.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Test/Unit/TemplateRoomChatViewModelTests.swift index ea39fe0e8..5a99f0a58 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Test/Unit/TemplateRoomChatViewModelTests.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Test/Unit/TemplateRoomChatViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,13 +14,12 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI class TemplateRoomChatViewModelTests: XCTestCase { - var service: MockTemplateRoomChatService! var viewModel: TemplateRoomChatViewModel! var context: TemplateRoomChatViewModel.Context! @@ -39,18 +38,17 @@ class TemplateRoomChatViewModelTests: XCTestCase { XCTAssertEqual(context.viewState.roomInitializationStatus, .initialized) } - func testSendMessageUpdatesReceived() throws { let bubblesPublisher: AnyPublisher<[[TemplateRoomChatBubble]], Never> = context.$viewState.map(\.bubbles).removeDuplicates().collect(2).first().eraseToAnyPublisher() let awaitDeferred = xcAwaitDeferred(bubblesPublisher) - let newMessage: String = "Let's Go" + let newMessage = "Let's Go" service.send(textMessage: newMessage) let result: [[TemplateRoomChatBubble]]? = try awaitDeferred() // Test that the update to the messages in turn updates the view's // the last bubble by appending another text item, asserting the body. - guard let item:TemplateRoomChatBubbleItem = result?.last?.last?.items.last, + guard let item: TemplateRoomChatBubbleItem = result?.last?.last?.items.last, case TemplateRoomChatBubbleItemContent.message(let message) = item.content, case let TemplateRoomChatMessageContent.text(text) = message else { XCTFail() diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift index fb70cf503..029b10715 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift @@ -14,11 +14,10 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI struct TemplateRoomChat: View { - // MARK: - Properties // MARK: Private @@ -32,7 +31,7 @@ struct TemplateRoomChat: View { var body: some View { VStack { - VStack{ + VStack { roomContent }.frame(maxHeight: .infinity) @@ -51,7 +50,6 @@ struct TemplateRoomChat: View { .animation(.easeOut(duration: 0.25)) .transition(.move(edge: .trailing)) .padding() - } .background(theme.colors.background) .navigationTitle(viewModel.viewState.roomName ?? "Chat") @@ -83,14 +81,14 @@ struct TemplateRoomChat: View { private var bubbleList: some View { ScrollViewReader { reader in - ScrollView{ + ScrollView { LazyVStack { ForEach(viewModel.viewState.bubbles) { bubble in TemplateRoomChatBubbleView(bubble: bubble) .id(bubble.id) } } - .onAppear{ + .onAppear { guard let lastBubbleId = viewModel.viewState.bubbles.last?.id else { return } reader.scrollTo(lastBubbleId, anchor: .bottom) @@ -116,7 +114,7 @@ struct TemplateRoomChat: View { } private var itemCount: Int { - return viewModel.viewState + viewModel.viewState .bubbles .map(\.items) .map(\.count) diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleContentView.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleContentView.swift index 59f4bac29..61ccfb816 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleContentView.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleContentView.swift @@ -17,7 +17,6 @@ import SwiftUI struct TemplateRoomChatBubbleContentView: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleImage.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleImage.swift index 9014e8209..5cd791eca 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleImage.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleImage.swift @@ -17,7 +17,6 @@ import SwiftUI struct TemplateRoomChatBubbleImage: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleMessage.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleMessage.swift index aa1cb44c6..8732f7452 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleMessage.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleMessage.swift @@ -17,7 +17,6 @@ import SwiftUI struct TemplateRoomChatBubbleMessage: View { - // MARK: - Properties // MARK: Private diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift index f7cd4387b..a208559c0 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift @@ -17,7 +17,6 @@ import SwiftUI struct TemplateRoomChatBubbleView: View { - // MARK: - Properties // MARK: Private @@ -29,10 +28,10 @@ struct TemplateRoomChatBubbleView: View { let bubble: TemplateRoomChatBubble var body: some View { - HStack(alignment: .top){ + HStack(alignment: .top) { AvatarImage(avatarData: bubble.sender.avatarData, size: .xSmall) .accessibility(identifier: "bubbleImage") - VStack(alignment: .leading){ + VStack(alignment: .leading) { Text(bubble.sender.displayName ?? "") .foregroundColor(theme.userColor(for: bubble.sender.id)) .font(theme.fonts.bodySB) @@ -42,7 +41,7 @@ struct TemplateRoomChatBubbleView: View { } Spacer() } - //add to a style + // add to a style .padding(.horizontal) .padding(.vertical, 8) .frame(maxWidth: .infinity) diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Coordinator/TemplateRoomListCoordinator.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Coordinator/TemplateRoomListCoordinator.swift index 8d08f3de2..9eec68a72 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Coordinator/TemplateRoomListCoordinator.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Coordinator/TemplateRoomListCoordinator.swift @@ -21,7 +21,6 @@ struct TemplateRoomListCoordinatorParameters { } final class TemplateRoomListCoordinator: Coordinator, Presentable { - // MARK: - Properties // MARK: Private @@ -59,12 +58,11 @@ final class TemplateRoomListCoordinator: Coordinator, Presentable { self.callback?(.didSelectRoom(roomId)) case .done: self.callback?(.done) - break } } } func toPresentable() -> UIViewController { - return self.templateRoomListHostingController + templateRoomListHostingController } } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/MockTemplateRoomListScreenState.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/MockTemplateRoomListScreenState.swift index f0b6b1406..8b8c0e2a3 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/MockTemplateRoomListScreenState.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/MockTemplateRoomListScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import Foundation import SwiftUI - /// Using an enum for the screen allows you define the different state cases with /// the relevant associated data for each case. enum MockTemplateRoomListScreenState: MockScreenState, CaseIterable { diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Service/MatrixSDK/TemplateRoomListService.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Service/MatrixSDK/TemplateRoomListService.swift index 8678e6ddd..f723d0853 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Service/MatrixSDK/TemplateRoomListService.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Service/MatrixSDK/TemplateRoomListService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,11 +14,10 @@ // limitations under the License. // -import Foundation import Combine +import Foundation class TemplateRoomListService: TemplateRoomListServiceProtocol { - // MARK: - Properties // MARK: Private @@ -27,6 +26,7 @@ class TemplateRoomListService: TemplateRoomListServiceProtocol { private var listenerReference: Any? // MARK: Public + private(set) var roomsSubject: CurrentValueSubject<[TemplateRoomListRoom], Never> // MARK: - Setup @@ -35,15 +35,13 @@ class TemplateRoomListService: TemplateRoomListServiceProtocol { self.session = session let unencryptedRooms = session.rooms - .filter({ !$0.summary.isEncrypted }) + .filter { !$0.summary.isEncrypted } .map(TemplateRoomListRoom.init(mxRoom:)) - self.roomsSubject = CurrentValueSubject(unencryptedRooms) + roomsSubject = CurrentValueSubject(unencryptedRooms) } - } -fileprivate extension TemplateRoomListRoom { - +private extension TemplateRoomListRoom { init(mxRoom: MXRoom) { self.init(id: mxRoom.roomId, avatar: mxRoom.avatarData, displayName: mxRoom.summary.displayname) } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Service/Mock/MockTemplateRoomListService.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Service/Mock/MockTemplateRoomListService.swift index 1fcd53cb1..064884561 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Service/Mock/MockTemplateRoomListService.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Service/Mock/MockTemplateRoomListService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,11 +14,10 @@ // limitations under the License. // -import Foundation import Combine +import Foundation class MockTemplateRoomListService: TemplateRoomListServiceProtocol { - static let mockRooms = [ TemplateRoomListRoom(id: "!aaabaa:matrix.org", avatar: MockAvatarInput.example, displayName: "Matrix Discussion"), TemplateRoomListRoom(id: "!zzasds:matrix.org", avatar: MockAvatarInput.example, displayName: "Element Mobile"), @@ -31,6 +30,6 @@ class MockTemplateRoomListService: TemplateRoomListServiceProtocol { } func simulateUpdate(rooms: [TemplateRoomListRoom]) { - self.roomsSubject.send(rooms) + roomsSubject.send(rooms) } } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Service/TemplateRoomListServiceProtocol.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Service/TemplateRoomListServiceProtocol.swift index ba4248446..2eed29bb5 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Service/TemplateRoomListServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Service/TemplateRoomListServiceProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import Foundation import Combine +import Foundation protocol TemplateRoomListServiceProtocol { var roomsSubject: CurrentValueSubject<[TemplateRoomListRoom], Never> { get } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListModels.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListModels.swift index 57452cc1c..6d1c67811 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListModels.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListViewModel.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListViewModel.swift index 14c84cc11..acb68aa85 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListViewModel.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListViewModel.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,15 +14,14 @@ // limitations under the License. // -import SwiftUI import Combine +import SwiftUI typealias TemplateRoomListViewModelType = StateStoreViewModel + Never, + TemplateRoomListViewAction> class TemplateRoomListViewModel: TemplateRoomListViewModelType, TemplateRoomListViewModelProtocol { - // MARK: - Properties // MARK: Private @@ -42,7 +41,7 @@ class TemplateRoomListViewModel: TemplateRoomListViewModelType, TemplateRoomList } private static func defaultState(templateRoomListService: TemplateRoomListServiceProtocol) -> TemplateRoomListViewState { - return TemplateRoomListViewState(rooms: templateRoomListService.roomsSubject.value) + TemplateRoomListViewState(rooms: templateRoomListService.roomsSubject.value) } private func startObservingRooms() { diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListViewModelProtocol.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListViewModelProtocol.swift index ee9e96060..217155848 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Test/UI/TemplateRoomListUITests.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Test/UI/TemplateRoomListUITests.swift index 8ee0012e2..9535644d4 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Test/UI/TemplateRoomListUITests.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Test/UI/TemplateRoomListUITests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class TemplateRoomListUITests: MockScreenTestCase { func testTemplateRoomListNoRooms() { @@ -29,8 +29,7 @@ class TemplateRoomListUITests: MockScreenTestCase { func testTemplateRoomListRooms() { app.goToScreenWithIdentifier(MockTemplateRoomListScreenState.rooms.title) - let displayNameCount = app.buttons.matching(identifier:"roomNameText").count + let displayNameCount = app.buttons.matching(identifier: "roomNameText").count XCTAssertEqual(displayNameCount, 3) } - } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Test/Unit/TemplateRoomListViewModelTests.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Test/Unit/TemplateRoomListViewModelTests.swift index 8f69bb605..e39303007 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Test/Unit/TemplateRoomListViewModelTests.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Test/Unit/TemplateRoomListViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,14 +14,14 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI class TemplateRoomListViewModelTests: XCTestCase { - private enum Constants { - } + private enum Constants { } + var service: MockTemplateRoomListService! var viewModel: TemplateRoomListViewModel! var context: TemplateRoomListViewModel.Context! @@ -46,7 +46,7 @@ class TemplateRoomListViewModelTests: XCTestCase { let updatedRooms = Array(MockTemplateRoomListService.mockRooms.dropLast()) let roomsPublisher = context.$viewState.map(\.rooms).removeDuplicates().collect(2).first() let awaitDeferred = xcAwaitDeferred(roomsPublisher) - service.simulateUpdate(rooms: updatedRooms) + service.simulateUpdate(rooms: updatedRooms) XCTAssertEqual(try awaitDeferred(), [MockTemplateRoomListService.mockRooms, updatedRooms]) } } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/View/TemplateRoomList.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/View/TemplateRoomList.swift index 9ba36a2b4..6ee720a72 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/View/TemplateRoomList.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/View/TemplateRoomList.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ import SwiftUI struct TemplateRoomList: View { - // MARK: - Properties // MARK: Private @@ -47,7 +46,7 @@ struct TemplateRoomList: View { .foregroundColor(theme.colors.primaryContent) .accessibility(identifier: "errorMessage") } else { - ScrollView{ + ScrollView { LazyVStack(spacing: 0) { ForEach(viewModel.viewState.rooms) { room in Button { @@ -66,7 +65,6 @@ struct TemplateRoomList: View { // MARK: - Previews struct TemplateRoomList_Previews: PreviewProvider { - static let stateRenderer = MockTemplateRoomListScreenState.stateRenderer static var previews: some View { stateRenderer.screenGroup(addNavigation: true) diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/View/TemplateRoomListRow.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/View/TemplateRoomListRow.swift index c7418e635..8e5f50a81 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/View/TemplateRoomListRow.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/View/TemplateRoomListRow.swift @@ -17,7 +17,6 @@ import SwiftUI struct TemplateRoomListRow: View { - // MARK: - Properties // MARK: Private @@ -30,14 +29,14 @@ struct TemplateRoomListRow: View { let displayName: String? var body: some View { - HStack{ + HStack { AvatarImage(avatarData: avatar, size: .medium) Text(displayName ?? "") .foregroundColor(theme.colors.primaryContent) - .accessibility(identifier: "roomNameText") + .accessibility(identifier: "roomNameText") Spacer() } - //add to a style + // add to a style .padding(.horizontal) .padding(.vertical, 8) .frame(maxWidth: .infinity) diff --git a/RiotSwiftUI/Modules/UserSessions/Common/DeviceType.swift b/RiotSwiftUI/Modules/UserSessions/Common/DeviceType.swift index 7410be89a..0f0685778 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/DeviceType.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/DeviceType.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift index f8679b6df..ba1d18ecd 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ struct UserSessionInfo: Identifiable { // MARK: - Properties var id: String { - return sessionId + sessionId } /// The session identifier @@ -56,7 +56,6 @@ struct UserSessionInfo: Identifiable { isVerified: Bool, lastSeenIP: String?, lastSeenTimestamp: TimeInterval?) { - self.sessionId = sessionId self.sessionName = sessionName self.deviceType = deviceType diff --git a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionLastActivityFormatter.swift b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionLastActivityFormatter.swift index a3482d466..3c63d13b0 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionLastActivityFormatter.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionLastActivityFormatter.swift @@ -29,7 +29,6 @@ class UserSessionLastActivityFormatter { /// Session last activity string func lastActivityDateString(from lastActivityTimestamp: TimeInterval) -> String { - let date = Date(timeIntervalSince1970: lastActivityTimestamp) return UserSessionLastActivityFormatter.lastActivityDateFormatter.string(from: date) diff --git a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionNameFormatter.swift b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionNameFormatter.swift index 375aa8034..04dbd79f6 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionNameFormatter.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionNameFormatter.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ import Foundation /// Enables to build user session name -class UserSessionNameFormatter { +class UserSessionNameFormatter { /// Session name with client name and session display name func sessionName(deviceType: DeviceType, sessionDisplayName: String?) -> String { let sessionName: String diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/DeviceAvatarView.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/DeviceAvatarView.swift index 24a142619..d9ee4961e 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/DeviceAvatarView.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/DeviceAvatarView.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import SwiftUI import DesignKit +import SwiftUI /// Avatar view for device struct DeviceAvatarView: View { @@ -28,7 +28,6 @@ struct DeviceAvatarView: View { var body: some View { ZStack(alignment: .bottomTrailing) { - // Device image VStack(alignment: .center) { viewData.deviceType.image @@ -40,7 +39,6 @@ struct DeviceAvatarView: View { // Verification badge if let isVerified = viewData.isVerified { - Image(isVerified ? Asset.Images.userSessionVerified.name : Asset.Images.userSessionUnverified.name) .frame(maxWidth: CGFloat(badgeSize), maxHeight: CGFloat(badgeSize)) .shapedBorder(color: theme.colors.system, borderWidth: 1, shape: Circle()) @@ -55,7 +53,7 @@ struct DeviceAvatarView: View { struct DeviceAvatarViewListPreview: View { var viewDataList: [DeviceAvatarViewData] { - return [ + [ DeviceAvatarViewData(deviceType: .desktop, isVerified: true), DeviceAvatarViewData(deviceType: .web, isVerified: true), DeviceAvatarViewData(deviceType: .mobile, isVerified: true), @@ -66,7 +64,7 @@ struct DeviceAvatarViewListPreview: View { var body: some View { HStack { VStack(alignment: .center, spacing: 20) { - DeviceAvatarView(viewData: DeviceAvatarViewData.init(deviceType: .web, isVerified: true)) + DeviceAvatarView(viewData: DeviceAvatarViewData(deviceType: .web, isVerified: true)) DeviceAvatarView(viewData: DeviceAvatarViewData(deviceType: .desktop, isVerified: false)) DeviceAvatarView(viewData: DeviceAvatarViewData(deviceType: .mobile, isVerified: true)) DeviceAvatarView(viewData: DeviceAvatarViewData(deviceType: .unknown, isVerified: false)) diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/DeviceAvatarViewData.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/DeviceAvatarViewData.swift index e4dd59b26..397e945dc 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/DeviceAvatarViewData.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/DeviceAvatarViewData.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/SeparatorLine.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/SeparatorLine.swift index 87d1a47d8..eac45ede2 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/SeparatorLine.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/SeparatorLine.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift index b423f4e7c..ee2255f10 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift @@ -14,40 +14,40 @@ // limitations under the License. // -import SwiftUI import DesignKit +import SwiftUI struct UserSessionCardView: View { @Environment(\.theme) var theme: ThemeSwiftUI var viewData: UserSessionCardViewData - var onVerifyAction: ((String) -> (Void))? = nil - var onViewDetailsAction: ((String) -> (Void))? = nil - var onLearnMoreAction: (() -> (Void))? = nil + var onVerifyAction: ((String) -> Void)? + var onViewDetailsAction: ((String) -> Void)? + var onLearnMoreAction: (() -> Void)? private var verificationStatusImageName: String { - return viewData.isVerified ? Asset.Images.userSessionVerified.name : Asset.Images.userSessionUnverified.name + viewData.isVerified ? Asset.Images.userSessionVerified.name : Asset.Images.userSessionUnverified.name } private var verificationStatusText: String { - return viewData.isVerified ? VectorL10n.userSessionVerified : VectorL10n.userSessionUnverified + viewData.isVerified ? VectorL10n.userSessionVerified : VectorL10n.userSessionUnverified } private var verificationStatusColor: Color { - return viewData.isVerified ? theme.colors.accent : theme.colors.alert + viewData.isVerified ? theme.colors.accent : theme.colors.alert } private var verificationStatusAdditionalInfoText: String { - return viewData.isVerified ? VectorL10n.userSessionVerifiedAdditionalInfo : VectorL10n.userSessionUnverifiedAdditionalInfo + viewData.isVerified ? VectorL10n.userSessionVerifiedAdditionalInfo : VectorL10n.userSessionUnverifiedAdditionalInfo } private var backgroundShape: RoundedRectangle { - return RoundedRectangle(cornerRadius: 8) + RoundedRectangle(cornerRadius: 8) } private var showExtraInformations: Bool { - return viewData.isCurrentSessionDisplayMode == false && (viewData.lastActivityDateString.isEmptyOrNil == false || viewData.lastSeenIPInfo.isEmptyOrNil == false) + viewData.isCurrentSessionDisplayMode == false && (viewData.lastActivityDateString.isEmptyOrNil == false || viewData.lastSeenIPInfo.isEmptyOrNil == false) } var body: some View { @@ -90,7 +90,7 @@ struct UserSessionCardView: View { .multilineTextAlignment(.center) } - if let lastSeenIPInfo = viewData.lastSeenIPInfo, lastSeenIPInfo.isEmpty == false { + if let lastSeenIPInfo = viewData.lastSeenIPInfo, lastSeenIPInfo.isEmpty == false { Text(lastSeenIPInfo) .font(theme.fonts.footnote) .foregroundColor(theme.colors.secondaryContent) diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift index f36cf0573..f5000095c 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ struct UserSessionCardViewData { private static let lastActivityDateFormatter = UserSessionLastActivityFormatter() var id: String { - return sessionId + sessionId } let sessionId: String diff --git a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift index a032dcfbd..5f2c810c1 100644 --- a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -111,6 +111,6 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return navigationRouter.toPresentable() + navigationRouter.toPresentable() } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift index c013711ae..f2302a473 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI struct UserSessionDetailsCoordinatorParameters { let userSessionInfo: UserSessionInfo @@ -63,6 +63,6 @@ final class UserSessionDetailsCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return userSessionDetailsHostingController + userSessionDetailsHostingController } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift index 3f9a2e634..7c58594fa 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,11 +33,11 @@ enum MockUserSessionDetailsScreenState: MockScreenState, CaseIterable { /// A list of screen state definitions static var allCases: [MockUserSessionDetailsScreenState] { // Each of the presence statuses - return [.allSections, .sessionSectionOnly] + [.allSections, .sessionSectionOnly] } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let currentSessionInfo: UserSessionInfo switch self { case .allSections: diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/UI/UserSessionDetailsUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/UI/UserSessionDetailsUITests.swift index eb9f33e79..6eb573fe9 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/UI/UserSessionDetailsUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/UI/UserSessionDetailsUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class UserSessionDetailsUITests: MockScreenTestCase { func test_longPressDetailsCell_CopiesValueToClipboard() throws { @@ -30,6 +30,6 @@ class UserSessionDetailsUITests: MockScreenTestCase { app.buttons["Copy"].tap() let clipboard = try XCTUnwrap(UIPasteboard.general.string) - XCTAssertEqual(clipboard,"iOS") + XCTAssertEqual(clipboard, "iOS") } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift index 70026cac0..6e50b6c39 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -96,7 +96,6 @@ class UserSessionDetailsViewModelTests: XCTestCase { isVerified: isVerified, lastSeenIP: lastSeenIP, lastSeenTimestamp: lastSeenTimestamp) - } private func sessionNameItem(sessionName: String) -> UserSessionDetailsSectionItemViewData { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsModels.swift index 0d994add3..456cd5e40 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,13 +20,11 @@ import Foundation // MARK: View model -enum UserSessionDetailsViewModelResult { -} +enum UserSessionDetailsViewModelResult { } // MARK: View -enum UserSessionDetailsViewAction { -} +enum UserSessionDetailsViewAction { } struct UserSessionDetailsViewState: BindableState, Equatable { let sections: [UserSessionDetailsSectionViewData] @@ -48,14 +46,14 @@ struct UserSessionDetailsSectionItemViewData: Identifiable { extension UserSessionDetailsSectionViewData: Equatable { static func == (lhs: UserSessionDetailsSectionViewData, rhs: UserSessionDetailsSectionViewData) -> Bool { lhs.header == rhs.header && - lhs.footer == rhs.footer && - lhs.items == rhs.items + lhs.footer == rhs.footer && + lhs.items == rhs.items } } extension UserSessionDetailsSectionItemViewData: Equatable { static func == (lhs: UserSessionDetailsSectionItemViewData, rhs: UserSessionDetailsSectionItemViewData) -> Bool { lhs.title == rhs.title && - lhs.value == rhs.value + lhs.value == rhs.value } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift index 3625f7026..91ff61511 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift @@ -17,8 +17,8 @@ import Foundation typealias UserSessionDetailsViewModelType = StateStoreViewModel + Never, + UserSessionDetailsViewAction> class UserSessionDetailsViewModel: UserSessionDetailsViewModelType, UserSessionDetailsViewModelProtocol { var completion: ((UserSessionDetailsViewModelResult) -> Void)? diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModelProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModelProtocol.swift index 0def51625..3fcef2eba 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetails.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetails.swift index fb89d2eaf..8801c9ef0 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetails.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetails.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetailsItem.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetailsItem.swift index c484ae35c..80ff02dc5 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetailsItem.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetailsItem.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,7 @@ struct UserSessionDetailsItem: View { } var body: some View { - HStack() { + HStack { Text(viewData.title) .font(theme.fonts.subheadline) .foregroundColor(theme.colors.secondaryContent) @@ -59,10 +59,10 @@ struct UserSessionDetailsItem_Previews: PreviewProvider { List { UserSessionDetailsItem(viewData: UserSessionDetailsSectionItemViewData(title: "Session name", value: "Element Web: Firefox on macOS")) - .listRowInsets(EdgeInsets()) + .listRowInsets(EdgeInsets()) UserSessionDetailsItem(viewData: UserSessionDetailsSectionItemViewData(title: "Session ID", value: "76c95352559d-react-7c57680b93db-js-b64dbdce74b0")) - .listRowInsets(EdgeInsets()) + .listRowInsets(EdgeInsets()) } .preferredColorScheme(.light) @@ -70,10 +70,10 @@ struct UserSessionDetailsItem_Previews: PreviewProvider { List { UserSessionDetailsItem(viewData: UserSessionDetailsSectionItemViewData(title: "Session name", value: "Element Web: Firefox on macOS")) - .listRowInsets(EdgeInsets()) + .listRowInsets(EdgeInsets()) UserSessionDetailsItem(viewData: UserSessionDetailsSectionItemViewData(title: "Session ID", value: "76c95352559d-react-7c57680b93db-js-b64dbdce74b0")) - .listRowInsets(EdgeInsets()) + .listRowInsets(EdgeInsets()) } .preferredColorScheme(.dark) .theme(.dark) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift index c7560d5f4..fe236ea94 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI struct UserSessionOverviewCoordinatorParameters { let userSessionInfo: UserSessionInfo @@ -58,7 +58,7 @@ final class UserSessionOverviewCoordinator: Coordinator, Presentable { MXLog.debug("[UserSessionOverviewCoordinator] UserSessionOverviewViewModel did complete with result: \(result).") switch result { case .verifyCurrentSession: - break // TODO + break // TODO: case let .showSessionDetails(sessionInfo: sessionInfo): self.completion?(.openSessionDetails(session: sessionInfo)) } @@ -66,7 +66,7 @@ final class UserSessionOverviewCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return hostingController + hostingController } // MARK: - Private diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift index 85f22dbd0..00f010d2b 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,28 +34,28 @@ enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { /// A list of screen state definitions static var allCases: [MockUserSessionOverviewScreenState] { // Each of the presence statuses - return [.currentSession, .otherSession] + [.currentSession, .otherSession] } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { + var screenView: ([Any], AnyView) { let viewModel: UserSessionOverviewViewModel switch self { case .currentSession: let currentSessionInfo = UserSessionInfo(sessionId: "session", - sessionName: "iOS", - deviceType: .mobile, - isVerified: false, - lastSeenIP: "10.0.0.10", - lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + sessionName: "iOS", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100) viewModel = UserSessionOverviewViewModel(userSessionInfo: currentSessionInfo, isCurrentSession: true) case .otherSession: let currentSessionInfo = UserSessionInfo(sessionId: "session", - sessionName: "Mac", - deviceType: .desktop, - isVerified: true, - lastSeenIP: "10.0.0.10", - lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + sessionName: "Mac", + deviceType: .desktop, + isVerified: true, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100) viewModel = UserSessionOverviewViewModel(userSessionInfo: currentSessionInfo, isCurrentSession: false) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift index fa9daf73e..2133c445b 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class UserSessionOverviewUITests: MockScreenTestCase { func test_whenCurrentSessionSelected_correctNavTittleDisplayed() { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift index 322b56766..f143f292e 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI @@ -47,10 +47,10 @@ class UserSessionOverviewViewModelTests: XCTestCase { private func createUserSessionInfo() -> UserSessionInfo { UserSessionInfo(sessionId: "session", - sessionName: "iOS", - deviceType: .mobile, - isVerified: false, - lastSeenIP: "10.0.0.10", - lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + sessionName: "iOS", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift index 9283df5c7..e7bf0c271 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift index 72cd6dc05..afed2b039 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift @@ -17,8 +17,8 @@ import SwiftUI typealias UserSessionOverviewViewModelType = StateStoreViewModel + Never, + UserSessionOverviewViewAction> class UserSessionOverviewViewModel: UserSessionOverviewViewModelType, UserSessionOverviewViewModelProtocol { private let userSessionInfo: UserSessionInfo diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModelProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModelProtocol.swift index 676695c1d..66eed3ba8 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift index 98a9d4695..b37a2099e 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift @@ -25,12 +25,12 @@ struct UserSessionOverview: View { ScrollView { UserSessionCardView(viewData: viewModel.viewState.cardViewData, onVerifyAction: { _ in - viewModel.send(viewAction: .verifyCurrentSession) - }, + viewModel.send(viewAction: .verifyCurrentSession) + }, onViewDetailsAction: { _ in - viewModel.send(viewAction: .viewSessionDetails) - }) - .padding(16) + viewModel.send(viewAction: .viewSessionDetails) + }) + .padding(16) SwiftUI.Section { UserSessionOverviewDisclosureCell(title: VectorL10n.userSessionOverviewSessionDetailsButtonTitle, onBackgroundTap: { viewModel.send(viewAction: .viewSessionDetails) @@ -40,8 +40,8 @@ struct UserSessionOverview: View { .background(theme.colors.system.ignoresSafeArea()) .frame(maxHeight: .infinity) .navigationTitle(viewModel.viewState.isCurrentSession ? - VectorL10n.userSessionOverviewCurrentSessionTitle : - VectorL10n.userSessionOverviewSessionTitle) + VectorL10n.userSessionOverviewCurrentSessionTitle : + VectorL10n.userSessionOverviewSessionTitle) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift index 027f5f0bd..e4146e9a7 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift @@ -20,13 +20,13 @@ struct UserSessionOverviewDisclosureCell: View { @Environment(\.theme) private var theme: ThemeSwiftUI let title: String - var onBackgroundTap: (() -> (Void))? = nil + var onBackgroundTap: (() -> Void)? var body: some View { - Button(action: { onBackgroundTap?()}) { + Button(action: { onBackgroundTap?() }) { VStack(spacing: 0) { SeparatorLine() - HStack() { + HStack { Text(title) .font(theme.fonts.body) .foregroundColor(theme.colors.primaryContent) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift index 74afa6c23..44717d9d0 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import SwiftUI import CommonKit +import SwiftUI struct UserSessionsOverviewCoordinatorParameters { let session: MXSession @@ -69,7 +69,7 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { } func toPresentable() -> UIViewController { - return hostingViewController + hostingViewController } // MARK: - Private @@ -88,15 +88,15 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { } private func showAllUnverifiedSessions() { - // TODO + // TODO: } private func showAllInactiveSessions() { - // TODO + // TODO: } private func startVerifyCurrentSession() { - // TODO + // TODO: } private func showCurrentSessionOverview(sessionInfo: UserSessionInfo) { @@ -108,6 +108,6 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { } private func showAllOtherSessions() { - // TODO + // TODO: } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/MockUserSessionsOverviewScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/MockUserSessionsOverviewScreenState.swift index a0b67dafc..710c54294 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/MockUserSessionsOverviewScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/MockUserSessionsOverviewScreenState.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,12 +33,12 @@ enum MockUserSessionsOverviewScreenState: MockScreenState, CaseIterable { /// A list of screen state definitions static var allCases: [MockUserSessionsOverviewScreenState] { // Each of the presence statuses - return [.verifiedSession] + [.verifiedSession] } /// Generate the view struct for the screen state. - var screenView: ([Any], AnyView) { - let service: MockUserSessionsOverviewService = MockUserSessionsOverviewService() + var screenView: ([Any], AnyView) { + let service = MockUserSessionsOverviewService() switch self { case .verifiedSession: break diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift index 2844b2269..aa387325d 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,10 +25,10 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { init(mxSession: MXSession) { self.mxSession = mxSession - overviewData = UserSessionsOverviewData(currentSession: nil, - unverifiedSessions: [], - inactiveSessions: [], - otherSessions: []) + overviewData = UserSessionsOverviewData(currentSession: nil, + unverifiedSessions: [], + inactiveSessions: [], + otherSessions: []) setupInitialOverviewData() } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift index 89b8d3aa3..a70df0cf5 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,14 +35,14 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { let inactiveSessionsInfo: [UserSessionInfo] = [] let otherSessionsInfo: [UserSessionInfo] = [ - UserSessionInfo(sessionId: "1", sessionName: "macOS", deviceType: .desktop, isVerified: true, lastSeenIP: "1.0.0.1", lastSeenTimestamp: (Date().timeIntervalSince1970 - 130000)), - UserSessionInfo(sessionId: "2", sessionName: "Firefox on Windows", deviceType: .web, isVerified: true, lastSeenIP: "2.0.0.2", lastSeenTimestamp: (Date().timeIntervalSince1970 - 100)), - UserSessionInfo(sessionId: "3", sessionName: "Android", deviceType: .mobile, isVerified: false, lastSeenIP: "3.0.0.3", lastSeenTimestamp: (Date().timeIntervalSince1970 - 10)) + UserSessionInfo(sessionId: "1", sessionName: "macOS", deviceType: .desktop, isVerified: true, lastSeenIP: "1.0.0.1", lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000), + UserSessionInfo(sessionId: "2", sessionName: "Firefox on Windows", deviceType: .web, isVerified: true, lastSeenIP: "2.0.0.2", lastSeenTimestamp: Date().timeIntervalSince1970 - 100), + UserSessionInfo(sessionId: "3", sessionName: "Android", deviceType: .mobile, isVerified: false, lastSeenIP: "3.0.0.3", lastSeenTimestamp: Date().timeIntervalSince1970 - 10) ] overviewData = UserSessionsOverviewData(currentSession: currentSessionInfo, - unverifiedSessions: unverifiedSessionsInfo, - inactiveSessions: inactiveSessionsInfo, - otherSessions: otherSessionsInfo) + unverifiedSessions: unverifiedSessionsInfo, + inactiveSessions: inactiveSessionsInfo, + otherSessions: otherSessionsInfo) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift index 9b3e81706..b1bc5f001 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/UI/UserSessionsOverviewUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/UI/UserSessionsOverviewUITests.swift index 136372d89..caede94d6 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/UI/UserSessionsOverviewUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/UI/UserSessionsOverviewUITests.swift @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import RiotSwiftUI +import XCTest class UserSessionsOverviewUITests: MockScreenTestCase { // TODO: diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionsOverviewViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionsOverviewViewModelTests.swift index d9c2ecb9c..1d24d1586 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionsOverviewViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionsOverviewViewModelTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ // limitations under the License. // -import XCTest import Combine +import XCTest @testable import RiotSwiftUI diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift index 8e66bd0e2..455a4b72a 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,7 +44,7 @@ struct UserSessionsOverviewViewState: BindableState { var otherSessionsViewData = [UserSessionListItemViewData]() - var showLoadingIndicator: Bool = false + var showLoadingIndicator = false } enum UserSessionsOverviewViewAction { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift index 855cc2e19..ee7a82717 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift @@ -17,8 +17,8 @@ import SwiftUI typealias UserSessionsOverviewViewModelType = StateStoreViewModel + Never, + UserSessionsOverviewViewAction> class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSessionsOverviewViewModelProtocol { private let userSessionsOverviewService: UserSessionsOverviewServiceProtocol @@ -88,7 +88,7 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess case .success(let overViewData): self.updateViewState(with: overViewData) case .failure(let error): - // TODO + // TODO: break } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModelProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModelProtocol.swift index ebbb790a9..01b4b45ba 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModelProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift index e60a366f3..297998f29 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift @@ -28,7 +28,7 @@ struct UserSessionListItem: View { let viewData: UserSessionListItemViewData - var onBackgroundTap: ((String) -> (Void))? = nil + var onBackgroundTap: ((String) -> Void)? var body: some View { Button { @@ -71,7 +71,7 @@ struct UserSessionListPreview: View { ForEach(userSessionsOverviewService.overviewData.otherSessions) { userSessionInfo in let viewData = UserSessionListItemViewData(userSessionInfo: userSessionInfo) - UserSessionListItem(viewData: viewData, onBackgroundTap: { sessionId in + UserSessionListItem(viewData: viewData, onBackgroundTap: { _ in }) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift index a682cd8e4..78a5fab53 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift @@ -22,7 +22,7 @@ struct UserSessionListItemViewData: Identifiable { private static let lastActivityDateFormatter = UserSessionLastActivityFormatter() var id: String { - return sessionId + sessionId } let sessionId: String @@ -38,7 +38,6 @@ struct UserSessionListItemViewData: Identifiable { deviceType: DeviceType, isVerified: Bool, lastActivityDate: TimeInterval?) { - self.sessionId = sessionId sessionName = Self.userSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName) sessionDetails = Self.buildSessionDetails(isVerified: isVerified, lastActivityDate: lastActivityDate) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift index 565718d69..0d2c5cabe 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -49,7 +49,6 @@ struct UserSessionsOverview: View { ScrollView { // Security recommendations section if viewModel.viewState.unverifiedSessionsViewData.isEmpty == false || viewModel.viewState.inactiveSessionsViewData.isEmpty == false { - // TODO: } @@ -65,7 +64,7 @@ struct UserSessionsOverview: View { .frame(maxHeight: .infinity) .navigationTitle(VectorL10n.userSessionsOverviewTitle) .activityIndicator(show: viewModel.viewState.showLoadingIndicator) - .onAppear() { + .onAppear { viewModel.send(viewAction: .viewAppeared) } } diff --git a/RiotSwiftUI/RiotSwiftUIApp.swift b/RiotSwiftUI/RiotSwiftUIApp.swift index d63504e2f..04ed5e39c 100644 --- a/RiotSwiftUI/RiotSwiftUIApp.swift +++ b/RiotSwiftUI/RiotSwiftUIApp.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,7 +39,7 @@ struct RiotSwiftUIApp: App { } class RiotSwiftUIAppDelegate: NSObject, UIApplicationDelegate { - func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { if ProcessInfo.processInfo.environment["IS_RUNNING_UI_TESTS"] == "1" { UIView.setAnimationsEnabled(false) } diff --git a/RiotSwiftUI/target.yml b/RiotSwiftUI/target.yml index 428fbbdb2..ed0ebee39 100644 --- a/RiotSwiftUI/target.yml +++ b/RiotSwiftUI/target.yml @@ -66,3 +66,15 @@ targets: configFiles: Debug: Debug.xcconfig Release: Release.xcconfig + + postBuildScripts: + - name: 🧹 SwiftFormat + runOnlyWhenInstalling: false + shell: /bin/sh + script: | + export PATH="$PATH:/opt/homebrew/bin" + if which swiftformat >/dev/null; then + swiftformat --lint --lenient "$PROJECT_DIR" + else + echo "warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat" + fi From 732dc3cba9521333e71831f6d9b0532760b6638f Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 27 Sep 2022 11:17:25 +0300 Subject: [PATCH 061/104] Removed unnecessary comments from RiotSwiftUI templates, fixed StateStoreViewModel typealias formatting --- .../AnalyticsPrompt/AnalyticsPromptViewModel.swift | 5 ++--- .../AuthenticationChoosePasswordViewModel.swift | 5 ++--- .../AuthenticationForgotPasswordViewModel.swift | 5 ++--- .../Login/AuthenticationLoginViewModel.swift | 4 +--- .../ReCaptcha/AuthenticationReCaptchaViewModel.swift | 4 +--- .../AuthenticationRegistrationViewModel.swift | 4 +--- .../AuthenticationServerSelectionViewModel.swift | 4 +--- .../AuthenticationSoftLogoutViewModel.swift | 5 ++--- .../Terms/AuthenticationTermsViewModel.swift | 4 +--- .../AuthenticationVerifyEmailViewModel.swift | 5 ++--- .../AuthenticationVerifyMsisdnViewModel.swift | 5 ++--- .../LiveLocationLabPromotionViewModel.swift | 4 +--- .../LiveLocationSharingViewerViewModel.swift | 5 ++--- .../LocationSharingViewModel.swift | 5 ++--- .../StaticLocationViewingViewModel.swift | 5 ++--- .../Avatar/OnboardingAvatarViewModel.swift | 4 +--- .../Celebration/OnboardingCelebrationViewModel.swift | 4 +--- .../OnboardingCongratulationsViewModel.swift | 4 +--- .../DisplayName/OnboardingDisplayNameViewModel.swift | 4 +--- .../OnboardingSplashScreenViewModel.swift | 4 +--- .../UseCase/OnboardingUseCaseViewModel.swift | 4 +--- .../AllChatsOnboardingViewModel.swift | 4 +--- .../Room/PollEditForm/PollEditFormViewModel.swift | 5 ++--- .../RoomAccessTypeChooserViewModel.swift | 5 ++--- .../Room/RoomUpgrade/RoomUpgradeViewModel.swift | 5 ++--- .../Room/TimelinePoll/TimelinePollViewModel.swift | 5 ++--- .../UserSuggestion/UserSuggestionViewModel.swift | 4 +--- .../ChangePassword/ChangePasswordViewModel.swift | 5 ++--- .../MatrixItemChooserViewModel.swift | 5 ++--- .../SpaceCreationEmailInvitesViewModel.swift | 5 ++--- .../ViewModel/SpaceCreationMenuViewModel.swift | 5 ++--- .../SpaceCreationPostProcessViewModel.swift | 5 ++--- .../ViewModel/SpaceCreationRoomsViewModel.swift | 5 ++--- .../ViewModel/SpaceCreationSettingsViewModel.swift | 4 +--- .../SpaceSelector/SpaceSelectorViewModel.swift | 4 +--- .../SpaceSettings/SpaceSettingsViewModel.swift | 5 ++--- .../TemplateSimpleScreenCoordinator.swift | 8 -------- .../TemplateSimpleScreenViewModel.swift | 12 +----------- .../View/TemplateSimpleScreen.swift | 8 -------- .../Coordinator/TemplateUserProfileCoordinator.swift | 8 -------- .../MatrixSDK/TemplateUserProfileService.swift | 8 -------- .../TemplateUserProfileViewModel.swift | 12 +----------- .../View/TemplateUserProfile.swift | 6 ------ .../View/TemplateUserProfileHeader.swift | 6 ------ .../View/TemplateUserProfilePresenceView.swift | 4 ---- .../Coordinator/TemplateRoomsCoordinator.swift | 8 -------- .../Coordinator/TemplateRoomChatCoordinator.swift | 8 -------- .../Service/MatrixSDK/TemplateRoomChatService.swift | 8 -------- .../TemplateRoomChat/TemplateRoomChatViewModel.swift | 12 +----------- .../TemplateRoomChat/View/TemplateRoomChat.swift | 6 ------ .../View/TemplateRoomChatBubbleContentView.swift | 6 ------ .../View/TemplateRoomChatBubbleImage.swift | 6 ------ .../View/TemplateRoomChatBubbleMessage.swift | 6 ------ .../View/TemplateRoomChatBubbleView.swift | 6 ------ .../Coordinator/TemplateRoomListCoordinator.swift | 8 -------- .../Service/MatrixSDK/TemplateRoomListService.swift | 8 -------- .../TemplateRoomList/TemplateRoomListViewModel.swift | 12 +----------- .../TemplateRoomList/View/TemplateRoomList.swift | 6 ------ .../TemplateRoomList/View/TemplateRoomListRow.swift | 6 ------ .../UserSessionDetailsViewModel.swift | 4 +--- .../UserSessionOverviewViewModel.swift | 4 +--- 61 files changed, 62 insertions(+), 288 deletions(-) diff --git a/RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptViewModel.swift b/RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptViewModel.swift index a0ce70a99..295aa27f0 100644 --- a/RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptViewModel.swift +++ b/RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptViewModel.swift @@ -17,9 +17,8 @@ import Combine import SwiftUI -typealias AnalyticsPromptViewModelType = StateStoreViewModel +typealias AnalyticsPromptViewModelType = StateStoreViewModel + class AnalyticsPromptViewModel: AnalyticsPromptViewModelType { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordViewModel.swift b/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordViewModel.swift index fb6fc88e2..9301a96d2 100644 --- a/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordViewModel.swift @@ -16,9 +16,8 @@ import SwiftUI -typealias AuthenticationChoosePasswordViewModelType = StateStoreViewModel +typealias AuthenticationChoosePasswordViewModelType = StateStoreViewModel + class AuthenticationChoosePasswordViewModel: AuthenticationChoosePasswordViewModelType, AuthenticationChoosePasswordViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordViewModel.swift b/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordViewModel.swift index fdc17d6c7..c9b393f7b 100644 --- a/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordViewModel.swift @@ -16,9 +16,8 @@ import SwiftUI -typealias AuthenticationForgotPasswordViewModelType = StateStoreViewModel +typealias AuthenticationForgotPasswordViewModelType = StateStoreViewModel + class AuthenticationForgotPasswordViewModel: AuthenticationForgotPasswordViewModelType, AuthenticationForgotPasswordViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift index d7d528a10..999fd0bf2 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift @@ -16,9 +16,7 @@ import SwiftUI -typealias AuthenticationLoginViewModelType = StateStoreViewModel +typealias AuthenticationLoginViewModelType = StateStoreViewModel class AuthenticationLoginViewModel: AuthenticationLoginViewModelType, AuthenticationLoginViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaViewModel.swift b/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaViewModel.swift index 873f278ba..d1ce75bec 100644 --- a/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaViewModel.swift @@ -16,9 +16,7 @@ import SwiftUI -typealias AuthenticationReCaptchaViewModelType = StateStoreViewModel +typealias AuthenticationReCaptchaViewModelType = StateStoreViewModel class AuthenticationReCaptchaViewModel: AuthenticationReCaptchaViewModelType, AuthenticationReCaptchaViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift b/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift index f792adb12..7052eee81 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift @@ -17,9 +17,7 @@ import Combine import SwiftUI -typealias AuthenticationRegistrationViewModelType = StateStoreViewModel +typealias AuthenticationRegistrationViewModelType = StateStoreViewModel class AuthenticationRegistrationViewModel: AuthenticationRegistrationViewModelType, AuthenticationRegistrationViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift index fdcc726b5..a30aedc21 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift @@ -16,9 +16,7 @@ import SwiftUI -typealias AuthenticationServerSelectionViewModelType = StateStoreViewModel +typealias AuthenticationServerSelectionViewModelType = StateStoreViewModel class AuthenticationServerSelectionViewModel: AuthenticationServerSelectionViewModelType, AuthenticationServerSelectionViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutViewModel.swift b/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutViewModel.swift index a984ac375..7de0f590c 100644 --- a/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutViewModel.swift @@ -16,9 +16,8 @@ import SwiftUI -typealias AuthenticationSoftLogoutViewModelType = StateStoreViewModel +typealias AuthenticationSoftLogoutViewModelType = StateStoreViewModel + class AuthenticationSoftLogoutViewModel: AuthenticationSoftLogoutViewModelType, AuthenticationSoftLogoutViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModel.swift b/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModel.swift index c868d8f4a..7a81cc935 100644 --- a/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModel.swift @@ -16,9 +16,7 @@ import SwiftUI -typealias AuthenticationTermsViewModelType = StateStoreViewModel +typealias AuthenticationTermsViewModelType = StateStoreViewModel class AuthenticationTermsViewModel: AuthenticationTermsViewModelType, AuthenticationTermsViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModel.swift b/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModel.swift index 269c5e0a1..44c70494e 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModel.swift @@ -16,9 +16,8 @@ import SwiftUI -typealias AuthenticationVerifyEmailViewModelType = StateStoreViewModel +typealias AuthenticationVerifyEmailViewModelType = StateStoreViewModel + class AuthenticationVerifyEmailViewModel: AuthenticationVerifyEmailViewModelType, AuthenticationVerifyEmailViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModel.swift b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModel.swift index 651a4935a..28aac8414 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModel.swift @@ -16,9 +16,8 @@ import SwiftUI -typealias AuthenticationVerifyMsisdnViewModelType = StateStoreViewModel +typealias AuthenticationVerifyMsisdnViewModelType = StateStoreViewModel + class AuthenticationVerifyMsisdnViewModel: AuthenticationVerifyMsisdnViewModelType, AuthenticationVerifyMsisdnViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionViewModel.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionViewModel.swift index 6a6b48a7d..b9572846a 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionViewModel.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionViewModel.swift @@ -16,9 +16,7 @@ import SwiftUI -typealias LiveLocationLabPromotionViewModelType = StateStoreViewModel +typealias LiveLocationLabPromotionViewModelType = StateStoreViewModel class LiveLocationLabPromotionViewModel: LiveLocationLabPromotionViewModelType, LiveLocationLabPromotionViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift index 9037b3af6..2fcadda69 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift @@ -18,9 +18,8 @@ import Combine import Mapbox import SwiftUI -typealias LiveLocationSharingViewerViewModelType = StateStoreViewModel +typealias LiveLocationSharingViewerViewModelType = StateStoreViewModel + class LiveLocationSharingViewerViewModel: LiveLocationSharingViewerViewModelType, LiveLocationSharingViewerViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingViewModel.swift b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingViewModel.swift index cbde1a243..feeaefe11 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingViewModel.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingViewModel.swift @@ -18,9 +18,8 @@ import Combine import CoreLocation import SwiftUI -typealias LocationSharingViewModelType = StateStoreViewModel +typealias LocationSharingViewModelType = StateStoreViewModel + class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift index 1f63e248e..c1719c8c2 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift @@ -17,9 +17,8 @@ import CoreLocation import SwiftUI -typealias StaticLocationViewingViewModelType = StateStoreViewModel +typealias StaticLocationViewingViewModelType = StateStoreViewModel + class StaticLocationViewingViewModel: StaticLocationViewingViewModelType, StaticLocationViewingViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarViewModel.swift b/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarViewModel.swift index 47bcf70a5..d2fadafc7 100644 --- a/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarViewModel.swift @@ -17,9 +17,7 @@ import Combine import SwiftUI -typealias OnboardingAvatarViewModelType = StateStoreViewModel +typealias OnboardingAvatarViewModelType = StateStoreViewModel class OnboardingAvatarViewModel: OnboardingAvatarViewModelType, OnboardingAvatarViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationViewModel.swift b/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationViewModel.swift index 4bba32dea..ac120cd63 100644 --- a/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationViewModel.swift @@ -16,9 +16,7 @@ import SwiftUI -typealias OnboardingCelebrationViewModelType = StateStoreViewModel +typealias OnboardingCelebrationViewModelType = StateStoreViewModel class OnboardingCelebrationViewModel: OnboardingCelebrationViewModelType, OnboardingCelebrationViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsViewModel.swift b/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsViewModel.swift index f61c3f4b0..1ea53a4cb 100644 --- a/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsViewModel.swift @@ -16,9 +16,7 @@ import SwiftUI -typealias OnboardingCongratulationsViewModelType = StateStoreViewModel +typealias OnboardingCongratulationsViewModelType = StateStoreViewModel class OnboardingCongratulationsViewModel: OnboardingCongratulationsViewModelType, OnboardingCongratulationsViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameViewModel.swift b/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameViewModel.swift index cfc7998f2..765d4c4e8 100644 --- a/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameViewModel.swift @@ -17,9 +17,7 @@ import Combine import SwiftUI -typealias OnboardingDisplayNameViewModelType = StateStoreViewModel +typealias OnboardingDisplayNameViewModelType = StateStoreViewModel class OnboardingDisplayNameViewModel: OnboardingDisplayNameViewModelType, OnboardingDisplayNameViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenViewModel.swift b/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenViewModel.swift index 6d1c936ea..95650f38f 100644 --- a/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenViewModel.swift @@ -17,9 +17,7 @@ import Combine import SwiftUI -typealias OnboardingSplashScreenViewModelType = StateStoreViewModel +typealias OnboardingSplashScreenViewModelType = StateStoreViewModel protocol OnboardingSplashScreenViewModelProtocol { var completion: ((OnboardingSplashScreenViewModelResult) -> Void)? { get set } diff --git a/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseViewModel.swift b/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseViewModel.swift index b96c35ca2..d3d61906d 100644 --- a/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseViewModel.swift @@ -16,9 +16,7 @@ import SwiftUI -typealias OnboardingUseCaseViewModelType = StateStoreViewModel +typealias OnboardingUseCaseViewModelType = StateStoreViewModel class OnboardingUseCaseViewModel: OnboardingUseCaseViewModelType, OnboardingUseCaseViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingViewModel.swift b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingViewModel.swift index 1e8111a7a..a91fa8d9d 100644 --- a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingViewModel.swift +++ b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingViewModel.swift @@ -17,9 +17,7 @@ import Combine import SwiftUI -typealias AllChatsOnboardingViewModelType = StateStoreViewModel +typealias AllChatsOnboardingViewModelType = StateStoreViewModel class AllChatsOnboardingViewModel: AllChatsOnboardingViewModelType, AllChatsOnboardingViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift b/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift index 0c9de40a7..7e48488a6 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift @@ -22,9 +22,8 @@ struct PollEditFormViewModelParameters { let pollDetails: EditFormPollDetails } -typealias PollEditFormViewModelType = StateStoreViewModel +typealias PollEditFormViewModelType = StateStoreViewModel + class PollEditFormViewModel: PollEditFormViewModelType, PollEditFormViewModelProtocol { private enum Constants { static let minAnswerOptionsCount = 2 diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift index c05ff9ac5..d05b9c7d6 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift @@ -17,9 +17,8 @@ import Combine import SwiftUI -typealias RoomAccessTypeChooserViewModelType = StateStoreViewModel +typealias RoomAccessTypeChooserViewModelType = StateStoreViewModel + class RoomAccessTypeChooserViewModel: RoomAccessTypeChooserViewModelType, RoomAccessTypeChooserViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModel.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModel.swift index c61dd0323..60f9485a4 100644 --- a/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModel.swift +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModel.swift @@ -17,9 +17,8 @@ import Combine import SwiftUI -typealias RoomUpgradeViewModelType = StateStoreViewModel +typealias RoomUpgradeViewModelType = StateStoreViewModel + class RoomUpgradeViewModel: RoomUpgradeViewModelType, RoomUpgradeViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift index e39ce8cbd..84d1016af 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift @@ -17,9 +17,8 @@ import Combine import SwiftUI -typealias TimelinePollViewModelType = StateStoreViewModel +typealias TimelinePollViewModelType = StateStoreViewModel + class TimelinePollViewModel: TimelinePollViewModelType, TimelinePollViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModel.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModel.swift index f0578380a..4eafdc054 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModel.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModel.swift @@ -17,9 +17,7 @@ import Combine import SwiftUI -typealias UserSuggestionViewModelType = StateStoreViewModel +typealias UserSuggestionViewModelType = StateStoreViewModel class UserSuggestionViewModel: UserSuggestionViewModelType, UserSuggestionViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModel.swift b/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModel.swift index 7882f2eab..4cfc43d67 100644 --- a/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModel.swift +++ b/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModel.swift @@ -16,9 +16,8 @@ import SwiftUI -typealias ChangePasswordViewModelType = StateStoreViewModel +typealias ChangePasswordViewModelType = StateStoreViewModel + class ChangePasswordViewModel: ChangePasswordViewModelType, ChangePasswordViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModel.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModel.swift index 0e487565e..bc1995750 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModel.swift @@ -17,9 +17,8 @@ import Combine import SwiftUI -typealias MatrixItemChooserViewModelType = StateStoreViewModel +typealias MatrixItemChooserViewModelType = StateStoreViewModel + class MatrixItemChooserViewModel: MatrixItemChooserViewModelType, MatrixItemChooserViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModel.swift index 239dadb6f..0787198b9 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModel.swift @@ -19,9 +19,8 @@ import Combine import SwiftUI -typealias SpaceCreationEmailInvitesViewModelType = StateStoreViewModel +typealias SpaceCreationEmailInvitesViewModelType = StateStoreViewModel + class SpaceCreationEmailInvitesViewModel: SpaceCreationEmailInvitesViewModelType, SpaceCreationEmailInvitesViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModel.swift index ffbf21dac..8364178e9 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModel.swift @@ -19,9 +19,8 @@ import Combine import SwiftUI -typealias SpaceCreationMenuViewModelType = StateStoreViewModel +typealias SpaceCreationMenuViewModelType = StateStoreViewModel + class SpaceCreationMenuViewModel: SpaceCreationMenuViewModelType, SpaceCreationMenuViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModel.swift index 25b1a85c4..3892963e9 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModel.swift @@ -19,9 +19,8 @@ import Combine import SwiftUI -typealias SpaceCreationPostProcessViewModelType = StateStoreViewModel +typealias SpaceCreationPostProcessViewModelType = StateStoreViewModel + class SpaceCreationPostProcessViewModel: SpaceCreationPostProcessViewModelType, SpaceCreationPostProcessViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModel.swift index 034c7a427..d20103994 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModel.swift @@ -19,9 +19,8 @@ import Combine import SwiftUI -typealias SpaceCreationRoomsViewModelType = StateStoreViewModel +typealias SpaceCreationRoomsViewModelType = StateStoreViewModel + class SpaceCreationRoomsViewModel: SpaceCreationRoomsViewModelType, SpaceCreationRoomsViewModelProtocol { // MARK: - Setup diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModel.swift index f3aca8133..beefa9188 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModel.swift @@ -19,9 +19,7 @@ import Combine import SwiftUI -typealias SpaceCreationSettingsViewModelType = StateStoreViewModel +typealias SpaceCreationSettingsViewModelType = StateStoreViewModel class SpaceCreationSettingsViewModel: SpaceCreationSettingsViewModelType, SpaceCreationSettingsViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModel.swift index bed0600ed..477eac8d2 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModel.swift @@ -17,9 +17,7 @@ import Combine import SwiftUI -typealias SpaceSelectorViewModelType = StateStoreViewModel +typealias SpaceSelectorViewModelType = StateStoreViewModel class SpaceSelectorViewModel: SpaceSelectorViewModelType, SpaceSelectorViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift index b35fad927..fbc853f2b 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift @@ -17,9 +17,8 @@ import Combine import SwiftUI -typealias SpaceSettingsViewModelType = StateStoreViewModel +typealias SpaceSettingsViewModelType = StateStoreViewModel + class SpaceSettingsViewModel: SpaceSettingsViewModelType, SpaceSettingsViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Template/SimpleScreenExample/Coordinator/TemplateSimpleScreenCoordinator.swift b/RiotSwiftUI/Modules/Template/SimpleScreenExample/Coordinator/TemplateSimpleScreenCoordinator.swift index ec2bd32d2..4833d31f9 100644 --- a/RiotSwiftUI/Modules/Template/SimpleScreenExample/Coordinator/TemplateSimpleScreenCoordinator.swift +++ b/RiotSwiftUI/Modules/Template/SimpleScreenExample/Coordinator/TemplateSimpleScreenCoordinator.swift @@ -22,25 +22,17 @@ struct TemplateSimpleScreenCoordinatorParameters { } final class TemplateSimpleScreenCoordinator: Coordinator, Presentable { - // MARK: - Properties - - // MARK: Private - private let parameters: TemplateSimpleScreenCoordinatorParameters private let templateSimpleScreenHostingController: UIViewController private var templateSimpleScreenViewModel: TemplateSimpleScreenViewModelProtocol private var indicatorPresenter: UserIndicatorTypePresenterProtocol private var loadingIndicator: UserIndicator? - - // MARK: Public // Must be used only internally var childCoordinators: [Coordinator] = [] var completion: ((TemplateSimpleScreenViewModelResult) -> Void)? - // MARK: - Setup - init(parameters: TemplateSimpleScreenCoordinatorParameters) { self.parameters = parameters diff --git a/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenViewModel.swift b/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenViewModel.swift index c90e70346..178c4215f 100644 --- a/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenViewModel.swift +++ b/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenViewModel.swift @@ -16,21 +16,11 @@ import SwiftUI -typealias TemplateSimpleScreenViewModelType = StateStoreViewModel +typealias TemplateSimpleScreenViewModelType = StateStoreViewModel class TemplateSimpleScreenViewModel: TemplateSimpleScreenViewModelType, TemplateSimpleScreenViewModelProtocol { - // MARK: - Properties - - // MARK: Private - - // MARK: Public - var completion: ((TemplateSimpleScreenViewModelResult) -> Void)? - // MARK: - Setup - init(promptType: TemplateSimpleScreenPromptType, initialCount: Int = 0) { super.init(initialViewState: TemplateSimpleScreenViewState(promptType: promptType, count: 0)) } diff --git a/RiotSwiftUI/Modules/Template/SimpleScreenExample/View/TemplateSimpleScreen.swift b/RiotSwiftUI/Modules/Template/SimpleScreenExample/View/TemplateSimpleScreen.swift index 4fe4302ef..8d8ef892b 100644 --- a/RiotSwiftUI/Modules/Template/SimpleScreenExample/View/TemplateSimpleScreen.swift +++ b/RiotSwiftUI/Modules/Template/SimpleScreenExample/View/TemplateSimpleScreen.swift @@ -17,10 +17,6 @@ import SwiftUI struct TemplateSimpleScreen: View { - // MARK: - Properties - - // MARK: Private - @Environment(\.theme) private var theme @Environment(\.horizontalSizeClass) private var horizontalSizeClass @@ -28,12 +24,8 @@ struct TemplateSimpleScreen: View { horizontalSizeClass == .regular ? 50 : 16 } - // MARK: Public - @ObservedObject var viewModel: TemplateSimpleScreenViewModel.Context - // MARK: Views - var body: some View { GeometryReader { geometry in VStack { diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Coordinator/TemplateUserProfileCoordinator.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Coordinator/TemplateUserProfileCoordinator.swift index 38525cc2c..b3098629b 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Coordinator/TemplateUserProfileCoordinator.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Coordinator/TemplateUserProfileCoordinator.swift @@ -22,25 +22,17 @@ struct TemplateUserProfileCoordinatorParameters { } final class TemplateUserProfileCoordinator: Coordinator, Presentable { - // MARK: - Properties - - // MARK: Private - private let parameters: TemplateUserProfileCoordinatorParameters private let templateUserProfileHostingController: UIViewController private var templateUserProfileViewModel: TemplateUserProfileViewModelProtocol private var indicatorPresenter: UserIndicatorTypePresenterProtocol private var loadingIndicator: UserIndicator? - - // MARK: Public // Must be used only internally var childCoordinators: [Coordinator] = [] var completion: (() -> Void)? - // MARK: - Setup - init(parameters: TemplateUserProfileCoordinatorParameters) { self.parameters = parameters let viewModel = TemplateUserProfileViewModel.makeTemplateUserProfileViewModel(templateUserProfileService: TemplateUserProfileService(session: parameters.session)) diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Service/MatrixSDK/TemplateUserProfileService.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Service/MatrixSDK/TemplateUserProfileService.swift index 409e868a5..954bdbb3d 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Service/MatrixSDK/TemplateUserProfileService.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Service/MatrixSDK/TemplateUserProfileService.swift @@ -18,15 +18,9 @@ import Combine import Foundation class TemplateUserProfileService: TemplateUserProfileServiceProtocol { - // MARK: - Properties - - // MARK: Private - private let session: MXSession private var listenerReference: Any? - // MARK: Public - var userId: String { session.myUser.userId } @@ -41,8 +35,6 @@ class TemplateUserProfileService: TemplateUserProfileServiceProtocol { private(set) var presenceSubject: CurrentValueSubject - // MARK: - Setup - init(session: MXSession) { self.session = session presenceSubject = CurrentValueSubject(TemplateUserProfilePresence(mxPresence: session.myUser.presence)) diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileViewModel.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileViewModel.swift index 554eac157..8ccb953c3 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileViewModel.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileViewModel.swift @@ -17,23 +17,13 @@ import Combine import SwiftUI -typealias TemplateUserProfileViewModelType = StateStoreViewModel +typealias TemplateUserProfileViewModelType = StateStoreViewModel class TemplateUserProfileViewModel: TemplateUserProfileViewModelType, TemplateUserProfileViewModelProtocol { - // MARK: - Properties - - // MARK: Private - private let templateUserProfileService: TemplateUserProfileServiceProtocol - // MARK: Public - var completion: ((TemplateUserProfileViewModelResult) -> Void)? - // MARK: - Setup - static func makeTemplateUserProfileViewModel(templateUserProfileService: TemplateUserProfileServiceProtocol) -> TemplateUserProfileViewModelProtocol { TemplateUserProfileViewModel(templateUserProfileService: templateUserProfileService) } diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfile.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfile.swift index 7bf54cd21..cf1b44d3d 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfile.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfile.swift @@ -17,14 +17,8 @@ import SwiftUI struct TemplateUserProfile: View { - // MARK: - Properties - - // MARK: Private - @Environment(\.theme) private var theme: ThemeSwiftUI - // MARK: Public - @ObservedObject var viewModel: TemplateUserProfileViewModel.Context var body: some View { diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfileHeader.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfileHeader.swift index e3ec295e2..a87e06605 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfileHeader.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfileHeader.swift @@ -17,13 +17,7 @@ import SwiftUI struct TemplateUserProfileHeader: View { - // MARK: - Properties - - // MARK: Private - @Environment(\.theme) private var theme: ThemeSwiftUI - - // MARK: Public let avatar: AvatarInputProtocol? let displayName: String? diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfilePresenceView.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfilePresenceView.swift index 1a49a4d18..7bc69abd1 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfilePresenceView.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/View/TemplateUserProfilePresenceView.swift @@ -17,10 +17,6 @@ import SwiftUI struct TemplateUserProfilePresenceView: View { - // MARK: - Properties - - // MARK: Public - let presence: TemplateUserProfilePresence var body: some View { diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinator.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinator.swift index 869766600..5f5df52bb 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinator.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinator.swift @@ -18,25 +18,17 @@ import UIKit @objcMembers final class TemplateRoomsCoordinator: Coordinator, Presentable { - // MARK: - Properties - - // MARK: Private - private let parameters: TemplateRoomsCoordinatorParameters private var navigationRouter: NavigationRouterType { parameters.navigationRouter } - // MARK: Public - // Must be used only internally var childCoordinators: [Coordinator] = [] var callback: (() -> Void)? - // MARK: - Setup - init(parameters: TemplateRoomsCoordinatorParameters) { self.parameters = parameters } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Coordinator/TemplateRoomChatCoordinator.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Coordinator/TemplateRoomChatCoordinator.swift index 2a8c79c7e..bca98511f 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Coordinator/TemplateRoomChatCoordinator.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Coordinator/TemplateRoomChatCoordinator.swift @@ -21,22 +21,14 @@ struct TemplateRoomChatCoordinatorParameters { } final class TemplateRoomChatCoordinator: Coordinator, Presentable { - // MARK: - Properties - - // MARK: Private - private let parameters: TemplateRoomChatCoordinatorParameters private let templateRoomChatHostingController: UIViewController private var templateRoomChatViewModel: TemplateRoomChatViewModelProtocol - - // MARK: Public // Must be used only internally var childCoordinators: [Coordinator] = [] var callback: (() -> Void)? - // MARK: - Setup - init(parameters: TemplateRoomChatCoordinatorParameters) { self.parameters = parameters let viewModel = TemplateRoomChatViewModel(templateRoomChatService: TemplateRoomChatService(room: parameters.room)) diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift index 1428b6f70..364aa98f8 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift @@ -18,17 +18,11 @@ import Combine import Foundation class TemplateRoomChatService: TemplateRoomChatServiceProtocol { - // MARK: - Properties - - // MARK: Private - private let room: MXRoom private let eventFormatter: EventFormatter private var timeline: MXEventTimeline? private var eventBatch: [MXEvent] private var roomListenerReference: Any? - - // MARK: Public private(set) var chatMessagesSubject: CurrentValueSubject<[TemplateRoomChatMessage], Never> private(set) var roomInitializationStatus: CurrentValueSubject @@ -36,8 +30,6 @@ class TemplateRoomChatService: TemplateRoomChatServiceProtocol { var roomName: String? { room.summary.displayname } - - // MARK: - Setup init(room: MXRoom) { self.room = room diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatViewModel.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatViewModel.swift index f0a3ecf3a..915fb92fd 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatViewModel.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatViewModel.swift @@ -17,27 +17,17 @@ import Combine import SwiftUI -typealias TemplateRoomChatViewModelType = StateStoreViewModel +typealias TemplateRoomChatViewModelType = StateStoreViewModel class TemplateRoomChatViewModel: TemplateRoomChatViewModelType, TemplateRoomChatViewModelProtocol { enum Constants { static let maxTimeBeforeNewBubble: TimeInterval = 5 * 60 } - - // MARK: - Properties - - // MARK: Private private let templateRoomChatService: TemplateRoomChatServiceProtocol - // MARK: Public - var callback: ((TemplateRoomChatViewModelAction) -> Void)? - // MARK: - Setup - init(templateRoomChatService: TemplateRoomChatServiceProtocol) { self.templateRoomChatService = templateRoomChatService super.init(initialViewState: Self.defaultState(templateRoomChatService: templateRoomChatService)) diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift index 029b10715..2e755636a 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift @@ -18,14 +18,8 @@ import Combine import SwiftUI struct TemplateRoomChat: View { - // MARK: - Properties - - // MARK: Private - @Environment(\.theme) private var theme: ThemeSwiftUI - // MARK: Public - @ObservedObject var viewModel: TemplateRoomChatViewModel.Context var presentedModally = false diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleContentView.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleContentView.swift index 61ccfb816..23e7c69cb 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleContentView.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleContentView.swift @@ -17,14 +17,8 @@ import SwiftUI struct TemplateRoomChatBubbleContentView: View { - // MARK: - Properties - - // MARK: Private - @Environment(\.theme) private var theme: ThemeSwiftUI - // MARK: Public - let bubbleItem: TemplateRoomChatBubbleItem var body: some View { diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleImage.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleImage.swift index 5cd791eca..f8e4edcb3 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleImage.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleImage.swift @@ -17,14 +17,8 @@ import SwiftUI struct TemplateRoomChatBubbleImage: View { - // MARK: - Properties - - // MARK: Private - @Environment(\.theme) private var theme: ThemeSwiftUI - // MARK: Public - let imageContent: TemplateRoomChatMessageImageContent var body: some View { diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleMessage.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleMessage.swift index 8732f7452..b57f3026c 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleMessage.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleMessage.swift @@ -17,14 +17,8 @@ import SwiftUI struct TemplateRoomChatBubbleMessage: View { - // MARK: - Properties - - // MARK: Private - @Environment(\.theme) private var theme: ThemeSwiftUI - // MARK: Public - let messageContent: TemplateRoomChatMessageTextContent var body: some View { diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift index a208559c0..70495bd25 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift @@ -17,14 +17,8 @@ import SwiftUI struct TemplateRoomChatBubbleView: View { - // MARK: - Properties - - // MARK: Private - @Environment(\.theme) private var theme: ThemeSwiftUI - // MARK: Public - let bubble: TemplateRoomChatBubble var body: some View { diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Coordinator/TemplateRoomListCoordinator.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Coordinator/TemplateRoomListCoordinator.swift index 9eec68a72..26bbbbb99 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Coordinator/TemplateRoomListCoordinator.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Coordinator/TemplateRoomListCoordinator.swift @@ -21,22 +21,14 @@ struct TemplateRoomListCoordinatorParameters { } final class TemplateRoomListCoordinator: Coordinator, Presentable { - // MARK: - Properties - - // MARK: Private - private let parameters: TemplateRoomListCoordinatorParameters private let templateRoomListHostingController: UIViewController private var templateRoomListViewModel: TemplateRoomListViewModelProtocol - - // MARK: Public // Must be used only internally var childCoordinators: [Coordinator] = [] var callback: ((TemplateRoomListCoordinatorAction) -> Void)? - // MARK: - Setup - init(parameters: TemplateRoomListCoordinatorParameters) { self.parameters = parameters let viewModel = TemplateRoomListViewModel(templateRoomListService: TemplateRoomListService(session: parameters.session)) diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Service/MatrixSDK/TemplateRoomListService.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Service/MatrixSDK/TemplateRoomListService.swift index f723d0853..cd8b13649 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Service/MatrixSDK/TemplateRoomListService.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Service/MatrixSDK/TemplateRoomListService.swift @@ -18,19 +18,11 @@ import Combine import Foundation class TemplateRoomListService: TemplateRoomListServiceProtocol { - // MARK: - Properties - - // MARK: Private - private let session: MXSession private var listenerReference: Any? - - // MARK: Public private(set) var roomsSubject: CurrentValueSubject<[TemplateRoomListRoom], Never> - // MARK: - Setup - init(session: MXSession) { self.session = session diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListViewModel.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListViewModel.swift index acb68aa85..8d6701cea 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListViewModel.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListViewModel.swift @@ -17,23 +17,13 @@ import Combine import SwiftUI -typealias TemplateRoomListViewModelType = StateStoreViewModel +typealias TemplateRoomListViewModelType = StateStoreViewModel class TemplateRoomListViewModel: TemplateRoomListViewModelType, TemplateRoomListViewModelProtocol { - // MARK: - Properties - - // MARK: Private - private let templateRoomListService: TemplateRoomListServiceProtocol - // MARK: Public - var callback: ((TemplateRoomListViewModelAction) -> Void)? - // MARK: - Setup - init(templateRoomListService: TemplateRoomListServiceProtocol) { self.templateRoomListService = templateRoomListService super.init(initialViewState: Self.defaultState(templateRoomListService: templateRoomListService)) diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/View/TemplateRoomList.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/View/TemplateRoomList.swift index 6ee720a72..721e2d27c 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/View/TemplateRoomList.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/View/TemplateRoomList.swift @@ -17,14 +17,8 @@ import SwiftUI struct TemplateRoomList: View { - // MARK: - Properties - - // MARK: Private - @Environment(\.theme) private var theme: ThemeSwiftUI - // MARK: Public - @ObservedObject var viewModel: TemplateRoomListViewModelType.Context var body: some View { diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/View/TemplateRoomListRow.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/View/TemplateRoomListRow.swift index 8e5f50a81..a8db7b8ba 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/View/TemplateRoomListRow.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/View/TemplateRoomListRow.swift @@ -17,14 +17,8 @@ import SwiftUI struct TemplateRoomListRow: View { - // MARK: - Properties - - // MARK: Private - @Environment(\.theme) private var theme: ThemeSwiftUI - // MARK: Public - let avatar: AvatarInputProtocol let displayName: String? diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift index 91ff61511..fde1dac63 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift @@ -16,9 +16,7 @@ import Foundation -typealias UserSessionDetailsViewModelType = StateStoreViewModel +typealias UserSessionDetailsViewModelType = StateStoreViewModel class UserSessionDetailsViewModel: UserSessionDetailsViewModelType, UserSessionDetailsViewModelProtocol { var completion: ((UserSessionDetailsViewModelResult) -> Void)? diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift index afed2b039..907ad325f 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift @@ -16,9 +16,7 @@ import SwiftUI -typealias UserSessionOverviewViewModelType = StateStoreViewModel +typealias UserSessionOverviewViewModelType = StateStoreViewModel class UserSessionOverviewViewModel: UserSessionOverviewViewModelType, UserSessionOverviewViewModelProtocol { private let userSessionInfo: UserSessionInfo From adf0aaf9b51264238b1225dd0de0ba31c454fc23 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 27 Sep 2022 11:46:22 +0300 Subject: [PATCH 062/104] Removed deprecated StateStoreViewModel dispatching and reducing --- .swiftformat | 2 +- .../AnalyticsPromptViewModel.swift | 2 +- ...uthenticationChoosePasswordViewModel.swift | 2 +- ...uthenticationForgotPasswordViewModel.swift | 2 +- .../Login/AuthenticationLoginViewModel.swift | 2 +- .../AuthenticationReCaptchaViewModel.swift | 2 +- .../AuthenticationRegistrationViewModel.swift | 2 +- ...thenticationServerSelectionViewModel.swift | 2 +- .../AuthenticationSoftLogoutViewModel.swift | 2 +- .../Terms/AuthenticationTermsViewModel.swift | 2 +- .../AuthenticationVerifyEmailViewModel.swift | 2 +- .../AuthenticationVerifyMsisdnViewModel.swift | 2 +- .../ViewModel/StateStoreViewModel.swift | 30 +--------- .../LiveLocationLabPromotionViewModel.swift | 2 +- .../LiveLocationSharingViewerViewModel.swift | 2 +- .../LocationSharingViewModel.swift | 2 +- .../StaticLocationViewingViewModel.swift | 2 +- .../Avatar/OnboardingAvatarViewModel.swift | 2 +- .../OnboardingCelebrationViewModel.swift | 2 +- .../OnboardingCongratulationsViewModel.swift | 2 +- .../OnboardingDisplayNameViewModel.swift | 2 +- .../OnboardingSplashScreenViewModel.swift | 2 +- .../UseCase/OnboardingUseCaseModels.swift | 4 -- .../UseCase/OnboardingUseCaseViewModel.swift | 6 +- .../AllChatsOnboardingViewModel.swift | 2 +- .../PollEditForm/PollEditFormViewModel.swift | 2 +- .../RoomAccessTypeChooserModels.swift | 7 --- .../RoomAccessTypeChooserViewModel.swift | 38 +++++------- .../RoomUpgrade/RoomUpgradeViewModel.swift | 2 +- .../TimelinePoll/TimelinePollViewModel.swift | 2 +- .../UserSuggestionViewModel.swift | 2 +- .../ChangePasswordViewModel.swift | 2 +- .../MatrixItemChooserViewModel.swift | 2 +- ...SpaceCreationEmailInvitesStateAction.swift | 24 -------- .../SpaceCreationEmailInvitesViewModel.swift | 28 ++++----- .../Model/SpaceCreationMenuStateAction.swift | 22 ------- .../SpaceCreationMenuViewModel.swift | 2 +- .../SpaceCreationPostProcessViewModel.swift | 34 +++++------ .../Model/SpaceCreationRoomsStateAction.swift | 21 ------- .../SpaceCreationRoomsViewModel.swift | 4 +- .../SpaceCreationSettingsStateAction.swift | 29 --------- .../SpaceCreationSettingsViewModel.swift | 59 ++++++++----------- .../SpaceSelectorViewModel.swift | 2 +- .../SpaceSettingsViewModel.swift | 2 +- .../TemplateSimpleScreenViewModel.swift | 2 +- .../TemplateUserProfileViewModel.swift | 2 +- .../TemplateRoomChatViewModel.swift | 2 +- .../TemplateRoomListViewModel.swift | 2 +- .../UserSessionDetailsViewModel.swift | 2 +- .../UserSessionOverviewViewModel.swift | 2 +- .../UserSessionsOverviewViewModel.swift | 4 +- 51 files changed, 109 insertions(+), 275 deletions(-) delete mode 100644 RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesStateAction.swift delete mode 100644 RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuStateAction.swift delete mode 100644 RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsStateAction.swift delete mode 100644 RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsStateAction.swift diff --git a/.swiftformat b/.swiftformat index 024e43468..1873b0a6d 100644 --- a/.swiftformat +++ b/.swiftformat @@ -1,6 +1,6 @@ --swiftversion 5.6 ---exclude CommonKit, Config, DesignKit, Riot, RiotNSE, RiotShareExtension, RiotTests, SiriIntents, Tools, Variants, vendor +--exclude build, CommonKit, Config, DesignKit, fastlane, Pods, Riot, RiotNSE, RiotShareExtension, RiotTests, SiriIntents, Tools, Variants, vendor --disable wrapMultiLineStatementBraces --disable hoistPatternLet diff --git a/RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptViewModel.swift b/RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptViewModel.swift index 295aa27f0..ab2937f62 100644 --- a/RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptViewModel.swift +++ b/RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptViewModel.swift @@ -17,7 +17,7 @@ import Combine import SwiftUI -typealias AnalyticsPromptViewModelType = StateStoreViewModel +typealias AnalyticsPromptViewModelType = StateStoreViewModel class AnalyticsPromptViewModel: AnalyticsPromptViewModelType { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordViewModel.swift b/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordViewModel.swift index 9301a96d2..328b17161 100644 --- a/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/ChoosePassword/AuthenticationChoosePasswordViewModel.swift @@ -16,7 +16,7 @@ import SwiftUI -typealias AuthenticationChoosePasswordViewModelType = StateStoreViewModel +typealias AuthenticationChoosePasswordViewModelType = StateStoreViewModel class AuthenticationChoosePasswordViewModel: AuthenticationChoosePasswordViewModelType, AuthenticationChoosePasswordViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordViewModel.swift b/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordViewModel.swift index c9b393f7b..e9ec459b3 100644 --- a/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/ForgotPassword/AuthenticationForgotPasswordViewModel.swift @@ -16,7 +16,7 @@ import SwiftUI -typealias AuthenticationForgotPasswordViewModelType = StateStoreViewModel +typealias AuthenticationForgotPasswordViewModelType = StateStoreViewModel class AuthenticationForgotPasswordViewModel: AuthenticationForgotPasswordViewModelType, AuthenticationForgotPasswordViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift index 999fd0bf2..6c5274d62 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift @@ -16,7 +16,7 @@ import SwiftUI -typealias AuthenticationLoginViewModelType = StateStoreViewModel +typealias AuthenticationLoginViewModelType = StateStoreViewModel class AuthenticationLoginViewModel: AuthenticationLoginViewModelType, AuthenticationLoginViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaViewModel.swift b/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaViewModel.swift index d1ce75bec..755675e1f 100644 --- a/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/ReCaptcha/AuthenticationReCaptchaViewModel.swift @@ -16,7 +16,7 @@ import SwiftUI -typealias AuthenticationReCaptchaViewModelType = StateStoreViewModel +typealias AuthenticationReCaptchaViewModelType = StateStoreViewModel class AuthenticationReCaptchaViewModel: AuthenticationReCaptchaViewModelType, AuthenticationReCaptchaViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift b/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift index 7052eee81..dcfe72a7a 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift @@ -17,7 +17,7 @@ import Combine import SwiftUI -typealias AuthenticationRegistrationViewModelType = StateStoreViewModel +typealias AuthenticationRegistrationViewModelType = StateStoreViewModel class AuthenticationRegistrationViewModel: AuthenticationRegistrationViewModelType, AuthenticationRegistrationViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift index a30aedc21..b7c6d7c09 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift @@ -16,7 +16,7 @@ import SwiftUI -typealias AuthenticationServerSelectionViewModelType = StateStoreViewModel +typealias AuthenticationServerSelectionViewModelType = StateStoreViewModel class AuthenticationServerSelectionViewModel: AuthenticationServerSelectionViewModelType, AuthenticationServerSelectionViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutViewModel.swift b/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutViewModel.swift index 7de0f590c..f5b7971dc 100644 --- a/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/SoftLogout/AuthenticationSoftLogoutViewModel.swift @@ -16,7 +16,7 @@ import SwiftUI -typealias AuthenticationSoftLogoutViewModelType = StateStoreViewModel +typealias AuthenticationSoftLogoutViewModelType = StateStoreViewModel class AuthenticationSoftLogoutViewModel: AuthenticationSoftLogoutViewModelType, AuthenticationSoftLogoutViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModel.swift b/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModel.swift index 7a81cc935..275eded69 100644 --- a/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModel.swift @@ -16,7 +16,7 @@ import SwiftUI -typealias AuthenticationTermsViewModelType = StateStoreViewModel +typealias AuthenticationTermsViewModelType = StateStoreViewModel class AuthenticationTermsViewModel: AuthenticationTermsViewModelType, AuthenticationTermsViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModel.swift b/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModel.swift index 44c70494e..b3a31d585 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModel.swift @@ -16,7 +16,7 @@ import SwiftUI -typealias AuthenticationVerifyEmailViewModelType = StateStoreViewModel +typealias AuthenticationVerifyEmailViewModelType = StateStoreViewModel class AuthenticationVerifyEmailViewModel: AuthenticationVerifyEmailViewModelType, AuthenticationVerifyEmailViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModel.swift b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModel.swift index 28aac8414..354813296 100644 --- a/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModel.swift @@ -16,7 +16,7 @@ import SwiftUI -typealias AuthenticationVerifyMsisdnViewModelType = StateStoreViewModel +typealias AuthenticationVerifyMsisdnViewModelType = StateStoreViewModel class AuthenticationVerifyMsisdnViewModel: AuthenticationVerifyMsisdnViewModelType, AuthenticationVerifyMsisdnViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Common/ViewModel/StateStoreViewModel.swift b/RiotSwiftUI/Modules/Common/ViewModel/StateStoreViewModel.swift index 30641c7f9..dd1b27d3e 100644 --- a/RiotSwiftUI/Modules/Common/ViewModel/StateStoreViewModel.swift +++ b/RiotSwiftUI/Modules/Common/ViewModel/StateStoreViewModel.swift @@ -73,7 +73,7 @@ class ViewModelContext: ObservableObject { /// a specific portion of state that can be safely bound to. /// If we decide to add more features to our state management (like doing state processing off the main thread) /// we can do it in this centralised place. -class StateStoreViewModel { +class StateStoreViewModel { typealias Context = ViewModelContext // MARK: - Properties @@ -104,34 +104,6 @@ class StateStoreViewModel { .store(in: &cancellables) } - /// Send state actions to modify the state within the reducer. - /// - Parameter action: The state action to send to the reducer. - @available(*, deprecated, message: "Mutate state directly instead") - func dispatch(action: StateAction) { - Self.reducer(state: &context.viewState, action: action) - } - - /// Send state actions from a publisher to modify the state within the reducer. - /// - Parameter actionPublisher: The publisher that produces actions to be sent to the reducer - @available(*, deprecated, message: "Mutate state directly instead") - func dispatch(actionPublisher: AnyPublisher) { - actionPublisher.sink { [weak self] action in - guard let self = self else { return } - Self.reducer(state: &self.context.viewState, action: action) - } - .store(in: &cancellables) - } - - /// Override to handle mutations to the `State` - /// - /// A redux style reducer, all modifications to state happen here. - /// - Parameters: - /// - state: The `inout` state to be modified, - /// - action: The action that defines which state modification should take place. - class func reducer(state: inout State, action: StateAction) { - // Default implementation, -no-op - } - /// Override to handles incoming `ViewAction`s from the `ViewModel`. /// - Parameter viewAction: The `ViewAction` to be processed in `ViewModel` implementation. func process(viewAction: ViewAction) { diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionViewModel.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionViewModel.swift index b9572846a..e3408be02 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionViewModel.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationLabPromotion/LiveLocationLabPromotionViewModel.swift @@ -16,7 +16,7 @@ import SwiftUI -typealias LiveLocationLabPromotionViewModelType = StateStoreViewModel +typealias LiveLocationLabPromotionViewModelType = StateStoreViewModel class LiveLocationLabPromotionViewModel: LiveLocationLabPromotionViewModelType, LiveLocationLabPromotionViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift index 2fcadda69..ebfd06220 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift @@ -18,7 +18,7 @@ import Combine import Mapbox import SwiftUI -typealias LiveLocationSharingViewerViewModelType = StateStoreViewModel +typealias LiveLocationSharingViewerViewModelType = StateStoreViewModel class LiveLocationSharingViewerViewModel: LiveLocationSharingViewerViewModelType, LiveLocationSharingViewerViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingViewModel.swift b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingViewModel.swift index feeaefe11..39dbea5c8 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingViewModel.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/LocationSharingViewModel.swift @@ -18,7 +18,7 @@ import Combine import CoreLocation import SwiftUI -typealias LocationSharingViewModelType = StateStoreViewModel +typealias LocationSharingViewModelType = StateStoreViewModel class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift index c1719c8c2..83bdb0110 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift @@ -17,7 +17,7 @@ import CoreLocation import SwiftUI -typealias StaticLocationViewingViewModelType = StateStoreViewModel +typealias StaticLocationViewingViewModelType = StateStoreViewModel class StaticLocationViewingViewModel: StaticLocationViewingViewModelType, StaticLocationViewingViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarViewModel.swift b/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarViewModel.swift index d2fadafc7..21d02db5c 100644 --- a/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/Avatar/OnboardingAvatarViewModel.swift @@ -17,7 +17,7 @@ import Combine import SwiftUI -typealias OnboardingAvatarViewModelType = StateStoreViewModel +typealias OnboardingAvatarViewModelType = StateStoreViewModel class OnboardingAvatarViewModel: OnboardingAvatarViewModelType, OnboardingAvatarViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationViewModel.swift b/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationViewModel.swift index ac120cd63..6bfaaf7ac 100644 --- a/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/Celebration/OnboardingCelebrationViewModel.swift @@ -16,7 +16,7 @@ import SwiftUI -typealias OnboardingCelebrationViewModelType = StateStoreViewModel +typealias OnboardingCelebrationViewModelType = StateStoreViewModel class OnboardingCelebrationViewModel: OnboardingCelebrationViewModelType, OnboardingCelebrationViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsViewModel.swift b/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsViewModel.swift index 1ea53a4cb..448cde79b 100644 --- a/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsViewModel.swift @@ -16,7 +16,7 @@ import SwiftUI -typealias OnboardingCongratulationsViewModelType = StateStoreViewModel +typealias OnboardingCongratulationsViewModelType = StateStoreViewModel class OnboardingCongratulationsViewModel: OnboardingCongratulationsViewModelType, OnboardingCongratulationsViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameViewModel.swift b/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameViewModel.swift index 765d4c4e8..6fb6e38a7 100644 --- a/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/DisplayName/OnboardingDisplayNameViewModel.swift @@ -17,7 +17,7 @@ import Combine import SwiftUI -typealias OnboardingDisplayNameViewModelType = StateStoreViewModel +typealias OnboardingDisplayNameViewModelType = StateStoreViewModel class OnboardingDisplayNameViewModel: OnboardingDisplayNameViewModelType, OnboardingDisplayNameViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenViewModel.swift b/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenViewModel.swift index 95650f38f..0d9c9c563 100644 --- a/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenViewModel.swift @@ -17,7 +17,7 @@ import Combine import SwiftUI -typealias OnboardingSplashScreenViewModelType = StateStoreViewModel +typealias OnboardingSplashScreenViewModelType = StateStoreViewModel protocol OnboardingSplashScreenViewModelProtocol { var completion: ((OnboardingSplashScreenViewModelResult) -> Void)? { get set } diff --git a/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseModels.swift b/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseModels.swift index 8ee4f408b..796ba8e72 100644 --- a/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseModels.swift +++ b/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseModels.swift @@ -20,10 +20,6 @@ import Foundation // MARK: View model -enum OnboardingUseCaseStateAction { - case viewAction(OnboardingUseCaseViewAction) -} - enum OnboardingUseCaseViewModelResult { case personalMessaging case workMessaging diff --git a/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseViewModel.swift b/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseViewModel.swift index d3d61906d..b7eb7eb2a 100644 --- a/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseViewModel.swift @@ -16,7 +16,7 @@ import SwiftUI -typealias OnboardingUseCaseViewModelType = StateStoreViewModel +typealias OnboardingUseCaseViewModelType = StateStoreViewModel class OnboardingUseCaseViewModel: OnboardingUseCaseViewModelType, OnboardingUseCaseViewModelProtocol { // MARK: - Properties @@ -41,8 +41,4 @@ class OnboardingUseCaseViewModel: OnboardingUseCaseViewModelType, OnboardingUseC completion?(result) } } - - override class func reducer(state: inout OnboardingUseCaseViewState, action: OnboardingUseCaseStateAction) { - // There is no mutable state to reduce :) - } } diff --git a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingViewModel.swift b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingViewModel.swift index a91fa8d9d..0ba19872a 100644 --- a/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingViewModel.swift +++ b/RiotSwiftUI/Modules/Room/AllChatsOnboarding/AllChatsOnboardingViewModel.swift @@ -17,7 +17,7 @@ import Combine import SwiftUI -typealias AllChatsOnboardingViewModelType = StateStoreViewModel +typealias AllChatsOnboardingViewModelType = StateStoreViewModel class AllChatsOnboardingViewModel: AllChatsOnboardingViewModelType, AllChatsOnboardingViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift b/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift index 7e48488a6..43ce0dc72 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift @@ -22,7 +22,7 @@ struct PollEditFormViewModelParameters { let pollDetails: EditFormPollDetails } -typealias PollEditFormViewModelType = StateStoreViewModel +typealias PollEditFormViewModelType = StateStoreViewModel class PollEditFormViewModel: PollEditFormViewModelType, PollEditFormViewModelProtocol { private enum Constants { diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserModels.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserModels.swift index 4ebad9737..0268cc754 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserModels.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserModels.swift @@ -42,13 +42,6 @@ enum RoomAccessTypeChooserCoordinatorAction { // MARK: - View model -/// Actions to be performed on the `ViewModel` State -enum RoomAccessTypeChooserStateAction { - case updateAccessItems([RoomAccessTypeChooserAccessItem]) - case updateShowUpgradeRoomAlert(Bool) - case updateWaitingMessage(String?) -} - /// Actions sent by the`ViewModel` to the `Coordinator`. enum RoomAccessTypeChooserViewModelAction { case spaceSelection(String, RoomAccessTypeChooserAccessType) diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift index d05b9c7d6..2bd0d483c 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift @@ -17,7 +17,7 @@ import Combine import SwiftUI -typealias RoomAccessTypeChooserViewModelType = StateStoreViewModel +typealias RoomAccessTypeChooserViewModelType = StateStoreViewModel class RoomAccessTypeChooserViewModel: RoomAccessTypeChooserViewModelType, RoomAccessTypeChooserViewModelProtocol { // MARK: - Properties @@ -47,10 +47,13 @@ class RoomAccessTypeChooserViewModel: RoomAccessTypeChooserViewModelType, RoomAc } private func startObservingService() { - let accessTypePublisher = roomAccessTypeChooserService.accessItemsSubject - .map(RoomAccessTypeChooserStateAction.updateAccessItems) - .eraseToAnyPublisher() - dispatch(actionPublisher: accessTypePublisher) + roomAccessTypeChooserService + .accessItemsSubject + .sink(receiveValue: { [weak self] accessItems in + self?.state.accessItems = accessItems + }) + .store(in: &cancellables) + roomAccessTypeChooserService .roomUpgradeRequiredSubject .sink { [weak self] isUpgradeRequired in @@ -59,10 +62,13 @@ class RoomAccessTypeChooserViewModel: RoomAccessTypeChooserViewModelType, RoomAc } } .store(in: &cancellables) - let waitingMessagePublisher = roomAccessTypeChooserService.waitingMessageSubject - .map(RoomAccessTypeChooserStateAction.updateWaitingMessage) - .eraseToAnyPublisher() - dispatch(actionPublisher: waitingMessagePublisher) + + roomAccessTypeChooserService.waitingMessageSubject + .sink(receiveValue: { [weak self] message in + self?.state.bindings.waitingMessage = message + self?.state.bindings.isLoading = message != nil + }) + .store(in: &cancellables) } // MARK: - Public @@ -77,19 +83,7 @@ class RoomAccessTypeChooserViewModel: RoomAccessTypeChooserViewModelType, RoomAc cancel() } } - - override class func reducer(state: inout RoomAccessTypeChooserViewState, action: RoomAccessTypeChooserStateAction) { - switch action { - case .updateAccessItems(let accessItems): - state.accessItems = accessItems - case .updateShowUpgradeRoomAlert(let show): - state.bindings.showUpgradeRoomAlert = show - case .updateWaitingMessage(let message): - state.bindings.waitingMessage = message - state.bindings.isLoading = message != nil - } - } - + func handleRoomUpgradeResult(_ result: RoomUpgradeCoordinatorResult) { switch result { case .cancel(let roomId): diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModel.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModel.swift index 60f9485a4..ea3097399 100644 --- a/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModel.swift +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModel.swift @@ -17,7 +17,7 @@ import Combine import SwiftUI -typealias RoomUpgradeViewModelType = StateStoreViewModel +typealias RoomUpgradeViewModelType = StateStoreViewModel class RoomUpgradeViewModel: RoomUpgradeViewModelType, RoomUpgradeViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift index 84d1016af..a86862cf4 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift @@ -17,7 +17,7 @@ import Combine import SwiftUI -typealias TimelinePollViewModelType = StateStoreViewModel +typealias TimelinePollViewModelType = StateStoreViewModel class TimelinePollViewModel: TimelinePollViewModelType, TimelinePollViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModel.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModel.swift index 4eafdc054..1e1f490fc 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModel.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModel.swift @@ -17,7 +17,7 @@ import Combine import SwiftUI -typealias UserSuggestionViewModelType = StateStoreViewModel +typealias UserSuggestionViewModelType = StateStoreViewModel class UserSuggestionViewModel: UserSuggestionViewModelType, UserSuggestionViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModel.swift b/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModel.swift index 4cfc43d67..6cfcea0e6 100644 --- a/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModel.swift +++ b/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModel.swift @@ -16,7 +16,7 @@ import SwiftUI -typealias ChangePasswordViewModelType = StateStoreViewModel +typealias ChangePasswordViewModelType = StateStoreViewModel class ChangePasswordViewModel: ChangePasswordViewModelType, ChangePasswordViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModel.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModel.swift index bc1995750..8ef52c045 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModel.swift @@ -17,7 +17,7 @@ import Combine import SwiftUI -typealias MatrixItemChooserViewModelType = StateStoreViewModel +typealias MatrixItemChooserViewModelType = StateStoreViewModel class MatrixItemChooserViewModel: MatrixItemChooserViewModelType, MatrixItemChooserViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesStateAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesStateAction.swift deleted file mode 100644 index b266fbfb7..000000000 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Model/SpaceCreationEmailInvitesStateAction.swift +++ /dev/null @@ -1,24 +0,0 @@ -// File created from SimpleUserProfileExample -// $ createScreen.sh Spaces/SpaceCreation/SpaceCreationEmailInvites SpaceCreationEmailInvites -// -// Copyright 2021 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -enum SpaceCreationEmailInvitesStateAction { - case updateEmailValidity(_ validity: [Bool]) - case updateLoading(_ loading: Bool) -} diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModel.swift index 0787198b9..92c906378 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModel.swift @@ -19,7 +19,7 @@ import Combine import SwiftUI -typealias SpaceCreationEmailInvitesViewModelType = StateStoreViewModel +typealias SpaceCreationEmailInvitesViewModelType = StateStoreViewModel class SpaceCreationEmailInvitesViewModel: SpaceCreationEmailInvitesViewModelType, SpaceCreationEmailInvitesViewModelProtocol { // MARK: - Properties @@ -40,12 +40,14 @@ class SpaceCreationEmailInvitesViewModel: SpaceCreationEmailInvitesViewModelType self.service = service super.init(initialViewState: SpaceCreationEmailInvitesViewModel.defaultState(creationParameters: creationParameters, service: service)) } - + private func setupServiceObserving() { - let publisher = service.isLoadingSubject - .map(SpaceCreationEmailInvitesStateAction.updateLoading) - .eraseToAnyPublisher() - dispatch(actionPublisher: publisher) + service + .isLoadingSubject + .sink(receiveValue: { [weak self] isLoading in + self?.state.loading = isLoading + }) + .store(in: &cancellables) } private static func defaultState(creationParameters: SpaceCreationParameters, service: SpaceCreationEmailInvitesServiceProtocol) -> SpaceCreationEmailInvitesViewState { @@ -74,21 +76,13 @@ class SpaceCreationEmailInvitesViewModel: SpaceCreationEmailInvitesViewModelType } } - override class func reducer(state: inout SpaceCreationEmailInvitesViewState, action: SpaceCreationEmailInvitesStateAction) { - switch action { - case .updateEmailValidity(let emailValidity): - state.emailAddressesValid = emailValidity - case .updateLoading(let isLoading): - state.loading = isLoading - } - } - private func done() { creationParameters.emailInvites = context.emailInvites creationParameters.inviteType = .email - let emailAddressesValidity = service.validate(context.emailInvites) - dispatch(action: .updateEmailValidity(emailAddressesValidity)) + let emailAddressesValidity = service.validate(context.emailInvites) + state.emailAddressesValid = emailAddressesValidity + if context.emailInvites.reduce(true, { $0 && $1.isEmpty }) { completion?(.done) } else if emailAddressesValidity.reduce(true, { $0 && $1 }) { diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuStateAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuStateAction.swift deleted file mode 100644 index 15c7a4ffc..000000000 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Model/SpaceCreationMenuStateAction.swift +++ /dev/null @@ -1,22 +0,0 @@ -// File created from TemplateAdvancedRoomsExample -// $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// -// Copyright 2021 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -/// Actions to be performed on the `ViewModel` State -enum SpaceCreationMenuStateAction { } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModel.swift index 8364178e9..b1bd7e395 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModel.swift @@ -19,7 +19,7 @@ import Combine import SwiftUI -typealias SpaceCreationMenuViewModelType = StateStoreViewModel +typealias SpaceCreationMenuViewModelType = StateStoreViewModel class SpaceCreationMenuViewModel: SpaceCreationMenuViewModelType, SpaceCreationMenuViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModel.swift index 3892963e9..c90e1926b 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModel.swift @@ -19,7 +19,7 @@ import Combine import SwiftUI -typealias SpaceCreationPostProcessViewModelType = StateStoreViewModel +typealias SpaceCreationPostProcessViewModelType = StateStoreViewModel class SpaceCreationPostProcessViewModel: SpaceCreationPostProcessViewModelType, SpaceCreationPostProcessViewModelProtocol { // MARK: - Properties @@ -57,10 +57,21 @@ class SpaceCreationPostProcessViewModel: SpaceCreationPostProcessViewModelType, } private func setupTasksObserving() { - let tasksUpdatePublisher = spaceCreationPostProcessService.tasksSubject - .map(SpaceCreationPostProcessStateAction.updateTasks) - .eraseToAnyPublisher() - dispatch(actionPublisher: tasksUpdatePublisher) + spaceCreationPostProcessService + .tasksSubject + .sink(receiveValue: { [weak self] tasks in + guard let self = self else { return } + + self.state.tasks = tasks + self.state.isFinished = tasks.first?.state == .failure || tasks.reduce(true) { result, task in result && task.isFinished } + self.state.errorCount = tasks.reduce(0) { result, task in result + (task.state == .failure ? 1 : 0) } + + NotificationCenter.default.post(name: SpaceCreationPostProcessViewModel.didUpdate, + object: nil, + userInfo: [SpaceCreationPostProcessViewModel.newStateKey: self.state]) + }) + .store(in: &cancellables) + updateNotificationObserver = NotificationCenter.default.addObserver(forName: SpaceCreationPostProcessViewModel.didUpdate, object: nil, queue: OperationQueue.main) { [weak self] notification in guard let self = self else { return @@ -100,19 +111,6 @@ class SpaceCreationPostProcessViewModel: SpaceCreationPostProcessViewModelType, } } - override class func reducer(state: inout SpaceCreationPostProcessViewState, action: SpaceCreationPostProcessStateAction) { - switch action { - case .updateTasks(let tasks): - state.tasks = tasks - state.isFinished = tasks.first?.state == .failure || tasks.reduce(true) { result, task in result && task.isFinished } - state.errorCount = tasks.reduce(0) { result, task in result + (task.state == .failure ? 1 : 0) } - } - - NotificationCenter.default.post(name: SpaceCreationPostProcessViewModel.didUpdate, object: nil, userInfo: [SpaceCreationPostProcessViewModel.newStateKey: state]) - - UILog.debug("[SpaceCreationPostProcessViewModel] reducer with action \(action) produced state: \(state)") - } - private func done(spaceId: String) { completion?(.done(spaceId)) } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsStateAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsStateAction.swift deleted file mode 100644 index 8cfcd7660..000000000 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Model/SpaceCreationRoomsStateAction.swift +++ /dev/null @@ -1,21 +0,0 @@ -// File created from SimpleUserProfileExample -// $ createScreen.sh Spaces/SpaceCreation/SpaceCreationRooms SpaceCreationRooms -// -// Copyright 2021 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -enum SpaceCreationRoomsStateAction { } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModel.swift index d20103994..d673cd2b2 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModel.swift @@ -19,7 +19,7 @@ import Combine import SwiftUI -typealias SpaceCreationRoomsViewModelType = StateStoreViewModel +typealias SpaceCreationRoomsViewModelType = StateStoreViewModel class SpaceCreationRoomsViewModel: SpaceCreationRoomsViewModelType, SpaceCreationRoomsViewModelProtocol { // MARK: - Setup @@ -59,8 +59,6 @@ class SpaceCreationRoomsViewModel: SpaceCreationRoomsViewModelType, SpaceCreatio done() } } - - override class func reducer(state: inout SpaceCreationRoomsViewState, action: SpaceCreationRoomsStateAction) { } // MARK: - Private diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsStateAction.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsStateAction.swift deleted file mode 100644 index 33fa8b36c..000000000 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Model/SpaceCreationSettingsStateAction.swift +++ /dev/null @@ -1,29 +0,0 @@ -// File created from TemplateAdvancedRoomsExample -// $ createSwiftUITwoScreen.sh Spaces/SpaceCreation SpaceCreation SpaceCreationMenu SpaceCreationSettings -// -// Copyright 2021 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import UIKit - -/// Actions to be performed on the `ViewModel` State -enum SpaceCreationSettingsStateAction { - case updateRoomNameError(String?) - case updateRoomDefaultAddress(String) - case updateAddressValidationStatus(SpaceCreationSettingsAddressValidationStatus) - case updateAvatar(AvatarInputProtocol) - case updateAvatarImage(UIImage?) -} diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModel.swift index beefa9188..6f5661a8d 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModel.swift @@ -19,7 +19,7 @@ import Combine import SwiftUI -typealias SpaceCreationSettingsViewModelType = StateStoreViewModel +typealias SpaceCreationSettingsViewModelType = StateStoreViewModel class SpaceCreationSettingsViewModel: SpaceCreationSettingsViewModelType, SpaceCreationSettingsViewModelProtocol { // MARK: - Properties @@ -44,20 +44,27 @@ class SpaceCreationSettingsViewModel: SpaceCreationSettingsViewModelType, SpaceC } private func setupServiceObserving() { - let defaultAddressUpdatePublisher = spaceCreationSettingsService.defaultAddressSubject - .map(SpaceCreationSettingsStateAction.updateRoomDefaultAddress) - .eraseToAnyPublisher() - dispatch(actionPublisher: defaultAddressUpdatePublisher) + spaceCreationSettingsService + .defaultAddressSubject + .sink(receiveValue: { [weak self] address in + self?.state.defaultAddress = address + }) + .store(in: &cancellables) - let addressValidationUpdatePublisher = spaceCreationSettingsService.addressValidationSubject - .map(SpaceCreationSettingsStateAction.updateAddressValidationStatus) - .eraseToAnyPublisher() - dispatch(actionPublisher: addressValidationUpdatePublisher) + spaceCreationSettingsService + .addressValidationSubject + .sink(receiveValue: { [weak self] status in + self?.state.addressMessage = status.message + self?.state.isAddressValid = status.isValid + }) + .store(in: &cancellables) - let avatarUpdatePublisher = spaceCreationSettingsService.avatarViewDataSubject - .map(SpaceCreationSettingsStateAction.updateAvatar) - .eraseToAnyPublisher() - dispatch(actionPublisher: avatarUpdatePublisher) + spaceCreationSettingsService + .avatarViewDataSubject + .sink(receiveValue: { [weak self] avatar in + self?.state.avatar = avatar + }) + .store(in: &cancellables) } private static func defaultState(creationParameters: SpaceCreationParameters, validationStatus: SpaceCreationSettingsAddressValidationStatus) -> SpaceCreationSettingsViewState { @@ -84,7 +91,7 @@ class SpaceCreationSettingsViewModel: SpaceCreationSettingsViewModelType, SpaceC func updateAvatarImage(with image: UIImage?) { creationParameters.userSelectedAvatar = image - dispatch(action: .updateAvatarImage(image)) + state.avatarImage = image } override func process(viewAction: SpaceCreationSettingsViewAction) { @@ -101,7 +108,7 @@ class SpaceCreationSettingsViewModel: SpaceCreationSettingsViewModelType, SpaceC spaceCreationSettingsService.roomName = newValue creationParameters.address = spaceCreationSettingsService.defaultAddressSubject.value creationParameters.name = newValue - dispatch(action: .updateRoomNameError(newValue.isEmpty ? VectorL10n.spacesCreationEmptyRoomNameError : nil)) + state.roomNameError = newValue.isEmpty ? VectorL10n.spacesCreationEmptyRoomNameError : nil case .addressChanged(let newValue): spaceCreationSettingsService.userDefinedAddress = newValue creationParameters.userDefinedAddress = newValue @@ -109,28 +116,12 @@ class SpaceCreationSettingsViewModel: SpaceCreationSettingsViewModelType, SpaceC creationParameters.topic = newValue } } - - override class func reducer(state: inout SpaceCreationSettingsViewState, action: SpaceCreationSettingsStateAction) { - switch action { - case .updateRoomNameError(let error): - state.roomNameError = error - case .updateRoomDefaultAddress(let defaultAddress): - state.defaultAddress = defaultAddress - case .updateAddressValidationStatus(let validationStatus): - state.addressMessage = validationStatus.message - state.isAddressValid = validationStatus.isValid - case .updateAvatar(let avatar): - state.avatar = avatar - case .updateAvatarImage(let image): - state.avatarImage = image - } - } - + // MARK: - Private private func done() { guard !context.roomName.isEmpty else { - dispatch(action: .updateRoomNameError(VectorL10n.spacesCreationEmptyRoomNameError)) + state.roomNameError = VectorL10n.spacesCreationEmptyRoomNameError return } @@ -143,7 +134,7 @@ class SpaceCreationSettingsViewModel: SpaceCreationSettingsViewModelType, SpaceC creationParameters.userDefinedAddress = context.address creationParameters.address = spaceCreationSettingsService.defaultAddressSubject.value - dispatch(action: .updateRoomNameError(nil)) + state.roomNameError = nil callback?(.done) } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModel.swift index 477eac8d2..562b4e76a 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModel.swift @@ -17,7 +17,7 @@ import Combine import SwiftUI -typealias SpaceSelectorViewModelType = StateStoreViewModel +typealias SpaceSelectorViewModelType = StateStoreViewModel class SpaceSelectorViewModel: SpaceSelectorViewModelType, SpaceSelectorViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift index fbc853f2b..c1ea68534 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift @@ -17,7 +17,7 @@ import Combine import SwiftUI -typealias SpaceSettingsViewModelType = StateStoreViewModel +typealias SpaceSettingsViewModelType = StateStoreViewModel class SpaceSettingsViewModel: SpaceSettingsViewModelType, SpaceSettingsViewModelProtocol { // MARK: - Properties diff --git a/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenViewModel.swift b/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenViewModel.swift index 178c4215f..995faef6c 100644 --- a/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenViewModel.swift +++ b/RiotSwiftUI/Modules/Template/SimpleScreenExample/TemplateSimpleScreenViewModel.swift @@ -16,7 +16,7 @@ import SwiftUI -typealias TemplateSimpleScreenViewModelType = StateStoreViewModel +typealias TemplateSimpleScreenViewModelType = StateStoreViewModel class TemplateSimpleScreenViewModel: TemplateSimpleScreenViewModelType, TemplateSimpleScreenViewModelProtocol { var completion: ((TemplateSimpleScreenViewModelResult) -> Void)? diff --git a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileViewModel.swift b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileViewModel.swift index 8ccb953c3..d827aed94 100644 --- a/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileViewModel.swift +++ b/RiotSwiftUI/Modules/Template/SimpleUserProfileExample/TemplateUserProfileViewModel.swift @@ -17,7 +17,7 @@ import Combine import SwiftUI -typealias TemplateUserProfileViewModelType = StateStoreViewModel +typealias TemplateUserProfileViewModelType = StateStoreViewModel class TemplateUserProfileViewModel: TemplateUserProfileViewModelType, TemplateUserProfileViewModelProtocol { private let templateUserProfileService: TemplateUserProfileServiceProtocol diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatViewModel.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatViewModel.swift index 915fb92fd..91da403b3 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatViewModel.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatViewModel.swift @@ -17,7 +17,7 @@ import Combine import SwiftUI -typealias TemplateRoomChatViewModelType = StateStoreViewModel +typealias TemplateRoomChatViewModelType = StateStoreViewModel class TemplateRoomChatViewModel: TemplateRoomChatViewModelType, TemplateRoomChatViewModelProtocol { enum Constants { diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListViewModel.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListViewModel.swift index 8d6701cea..853abcf51 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListViewModel.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/TemplateRoomListViewModel.swift @@ -17,7 +17,7 @@ import Combine import SwiftUI -typealias TemplateRoomListViewModelType = StateStoreViewModel +typealias TemplateRoomListViewModelType = StateStoreViewModel class TemplateRoomListViewModel: TemplateRoomListViewModelType, TemplateRoomListViewModelProtocol { private let templateRoomListService: TemplateRoomListServiceProtocol diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift index fde1dac63..2772c489d 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift @@ -16,7 +16,7 @@ import Foundation -typealias UserSessionDetailsViewModelType = StateStoreViewModel +typealias UserSessionDetailsViewModelType = StateStoreViewModel class UserSessionDetailsViewModel: UserSessionDetailsViewModelType, UserSessionDetailsViewModelProtocol { var completion: ((UserSessionDetailsViewModelResult) -> Void)? diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift index 907ad325f..1b998cad9 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift @@ -16,7 +16,7 @@ import SwiftUI -typealias UserSessionOverviewViewModelType = StateStoreViewModel +typealias UserSessionOverviewViewModelType = StateStoreViewModel class UserSessionOverviewViewModel: UserSessionOverviewViewModelType, UserSessionOverviewViewModelProtocol { private let userSessionInfo: UserSessionInfo diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift index ee7a82717..47b5e7ad5 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift @@ -16,9 +16,7 @@ import SwiftUI -typealias UserSessionsOverviewViewModelType = StateStoreViewModel +typealias UserSessionsOverviewViewModelType = StateStoreViewModel class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSessionsOverviewViewModelProtocol { private let userSessionsOverviewService: UserSessionsOverviewServiceProtocol From e0caf5980efabdd297f52075627eb8d58e1d0f0d Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Tue, 27 Sep 2022 13:57:54 +0300 Subject: [PATCH 063/104] Renamed userSession and userSessionInfo to session --- .../Common/View/UserSessionCardView.swift | 9 ++++-- .../Common/View/UserSessionCardViewData.swift | 16 +++++----- .../UserSessionsFlowCoordinator.swift | 4 +-- .../UserSessionDetailsCoordinator.swift | 4 +-- .../MockUserSessionDetailsScreenState.swift | 30 +++++++++---------- .../UserSessionDetailsViewModelTests.swift | 6 ++-- .../UserSessionDetailsViewModel.swift | 20 ++++++------- .../UserSessionOverviewCoordinator.swift | 8 ++--- .../MockUserSessionOverviewScreenState.swift | 8 ++--- .../UserSessionOverviewViewModelTests.swift | 6 ++-- .../UserSessionOverviewModels.swift | 2 +- .../UserSessionOverviewViewModel.swift | 10 +++---- .../UserSessionsOverviewCoordinator.swift | 16 +++++----- .../UserSessionsOverviewModels.swift | 4 +-- .../UserSessionsOverviewViewModel.swift | 10 +++---- .../View/UserSessionListItem.swift | 2 +- .../View/UserSessionListItemViewData.swift | 4 +-- 17 files changed, 82 insertions(+), 77 deletions(-) diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift index ee2255f10..f082a7306 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift @@ -136,9 +136,14 @@ struct UserSessionCardViewPreview: View { let viewData: UserSessionCardViewData init(isCurrentSessionInfo: Bool = false) { - let currentSessionInfo = UserSessionInfo(sessionId: "alice", sessionName: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + let session = UserSessionInfo(sessionId: "alice", + sessionName: "iOS", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100) - viewData = UserSessionCardViewData(userSessionInfo: currentSessionInfo, isCurrentSessionDisplayMode: isCurrentSessionInfo) + viewData = UserSessionCardViewData(session: session, isCurrentSessionDisplayMode: isCurrentSessionInfo) } var body: some View { diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift index f5000095c..dd259fb8f 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift @@ -26,7 +26,7 @@ struct UserSessionCardViewData { } let sessionId: String - + let sessionName: String let isVerified: Bool @@ -66,13 +66,13 @@ struct UserSessionCardViewData { } extension UserSessionCardViewData { - init(userSessionInfo: UserSessionInfo, isCurrentSessionDisplayMode: Bool = false) { - self.init(sessionId: userSessionInfo.sessionId, - sessionDisplayName: userSessionInfo.sessionName, - deviceType: userSessionInfo.deviceType, - isVerified: userSessionInfo.isVerified, - lastActivityTimestamp: userSessionInfo.lastSeenTimestamp, - lastSeenIP: userSessionInfo.lastSeenIP, + init(session: UserSessionInfo, isCurrentSessionDisplayMode: Bool = false) { + self.init(sessionId: session.sessionId, + sessionDisplayName: session.sessionName, + deviceType: session.deviceType, + isVerified: session.isVerified, + lastActivityTimestamp: session.lastSeenTimestamp, + lastSeenIP: session.lastSeenIP, isCurrentSessionDisplayMode: isCurrentSessionDisplayMode) } } diff --git a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift index 5f2c810c1..60861b79c 100644 --- a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift @@ -66,7 +66,7 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { } private func createUserSessionDetailsCoordinator(session: UserSessionInfo) -> UserSessionDetailsCoordinator { - let parameters = UserSessionDetailsCoordinatorParameters(userSessionInfo: session) + let parameters = UserSessionDetailsCoordinatorParameters(session: session) return UserSessionDetailsCoordinator(parameters: parameters) } @@ -83,7 +83,7 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { } private func createUserSessionOverviewCoordinator(session: UserSessionInfo, isCurrentSession: Bool) -> UserSessionOverviewCoordinator { - let parameters = UserSessionOverviewCoordinatorParameters(userSessionInfo: session, isCurrentSession: isCurrentSession) + let parameters = UserSessionOverviewCoordinatorParameters(session: session, isCurrentSession: isCurrentSession) return UserSessionOverviewCoordinator(parameters: parameters) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift index f2302a473..bffc0ed56 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift @@ -18,7 +18,7 @@ import CommonKit import SwiftUI struct UserSessionDetailsCoordinatorParameters { - let userSessionInfo: UserSessionInfo + let session: UserSessionInfo } final class UserSessionDetailsCoordinator: Coordinator, Presentable { @@ -40,7 +40,7 @@ final class UserSessionDetailsCoordinator: Coordinator, Presentable { init(parameters: UserSessionDetailsCoordinatorParameters) { self.parameters = parameters - let viewModel = UserSessionDetailsViewModel(userSessionInfo: parameters.userSessionInfo) + let viewModel = UserSessionDetailsViewModel(session: parameters.session) let view = UserSessionDetails(viewModel: viewModel.context) userSessionDetailsViewModel = viewModel userSessionDetailsHostingController = VectorHostingController(rootView: view) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift index 7c58594fa..8d0478560 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift @@ -38,29 +38,29 @@ enum MockUserSessionDetailsScreenState: MockScreenState, CaseIterable { /// Generate the view struct for the screen state. var screenView: ([Any], AnyView) { - let currentSessionInfo: UserSessionInfo + let session: UserSessionInfo switch self { case .allSections: - currentSessionInfo = UserSessionInfo(sessionId: "session", - sessionName: "iOS", - deviceType: .mobile, - isVerified: false, - lastSeenIP: "10.0.0.10", - lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + session = UserSessionInfo(sessionId: "session", + sessionName: "iOS", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100) case .sessionSectionOnly: - currentSessionInfo = UserSessionInfo(sessionId: "session", - sessionName: "iOS", - deviceType: .mobile, - isVerified: false, - lastSeenIP: nil, - lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + session = UserSessionInfo(sessionId: "session", + sessionName: "iOS", + deviceType: .mobile, + isVerified: false, + lastSeenIP: nil, + lastSeenTimestamp: Date().timeIntervalSince1970 - 100) } - let viewModel = UserSessionDetailsViewModel(userSessionInfo: currentSessionInfo) + let viewModel = UserSessionDetailsViewModel(session: session) // can simulate service and viewModel actions here if needs be. return ( - [currentSessionInfo], + [session], AnyView(UserSessionDetails(viewModel: viewModel.context)) ) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift index 6e50b6c39..32d427faf 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift @@ -32,7 +32,7 @@ class UserSessionDetailsViewModelTests: XCTestCase { footer: VectorL10n.userSessionDetailsSessionSectionFooter, items: sessionItems)) let expectedModel = UserSessionDetailsViewState(sections: sections) - let sut = UserSessionDetailsViewModel(userSessionInfo: userSessionInfo) + let sut = UserSessionDetailsViewModel(session: userSessionInfo) XCTAssertEqual(sut.state, expectedModel) } @@ -52,7 +52,7 @@ class UserSessionDetailsViewModelTests: XCTestCase { items: sessionItems)) let expectedModel = UserSessionDetailsViewState(sections: sections) - let sut = UserSessionDetailsViewModel(userSessionInfo: userSessionInfo) + let sut = UserSessionDetailsViewModel(session: userSessionInfo) XCTAssertEqual(sut.state, expectedModel) } @@ -79,7 +79,7 @@ class UserSessionDetailsViewModelTests: XCTestCase { items: deviceSectionItems)) let expectedModel = UserSessionDetailsViewState(sections: sections) - let sut = UserSessionDetailsViewModel(userSessionInfo: userSessionInfo) + let sut = UserSessionDetailsViewModel(session: userSessionInfo) XCTAssertEqual(sut.state, expectedModel) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift index 2772c489d..66a5d7559 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift @@ -21,46 +21,46 @@ typealias UserSessionDetailsViewModelType = StateStoreViewModel Void)? - init(userSessionInfo: UserSessionInfo) { + init(session: UserSessionInfo) { super.init(initialViewState: UserSessionDetailsViewState(sections: [])) - updateViewState(userSessionInfo: userSessionInfo) + updateViewState(session: session) } // MARK: - Public // MARK: - Private - private func updateViewState(userSessionInfo: UserSessionInfo) { + private func updateViewState(session: UserSessionInfo) { var sections = [UserSessionDetailsSectionViewData]() - sections.append(sessionSection(userSessionInfo: userSessionInfo)) + sections.append(sessionSection(session: session)) - if let deviceSection = deviceSection(userSessionInfo: userSessionInfo) { + if let deviceSection = deviceSection(session: session) { sections.append(deviceSection) } state = UserSessionDetailsViewState(sections: sections) } - private func sessionSection(userSessionInfo: UserSessionInfo) -> UserSessionDetailsSectionViewData { + private func sessionSection(session: UserSessionInfo) -> UserSessionDetailsSectionViewData { var sessionItems = [UserSessionDetailsSectionItemViewData]() - if let sessionName = userSessionInfo.sessionName { + if let sessionName = session.sessionName { sessionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.userSessionDetailsSessionName, value: sessionName)) } sessionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.keyVerificationManuallyVerifyDeviceIdTitle, - value: userSessionInfo.sessionId)) + value: session.sessionId)) return UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader, footer: VectorL10n.userSessionDetailsSessionSectionFooter, items: sessionItems) } - private func deviceSection(userSessionInfo: UserSessionInfo) -> UserSessionDetailsSectionViewData? { + private func deviceSection(session: UserSessionInfo) -> UserSessionDetailsSectionViewData? { var deviceSectionItems = [UserSessionDetailsSectionItemViewData]() - if let lastSeenIP = userSessionInfo.lastSeenIP { + if let lastSeenIP = session.lastSeenIP { deviceSectionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.userSessionDetailsDeviceIpAddress, value: lastSeenIP)) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift index fe236ea94..6f3acf1c5 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift @@ -18,7 +18,7 @@ import CommonKit import SwiftUI struct UserSessionOverviewCoordinatorParameters { - let userSessionInfo: UserSessionInfo + let session: UserSessionInfo let isCurrentSession: Bool } @@ -41,7 +41,7 @@ final class UserSessionOverviewCoordinator: Coordinator, Presentable { init(parameters: UserSessionOverviewCoordinatorParameters) { self.parameters = parameters - viewModel = UserSessionOverviewViewModel(userSessionInfo: parameters.userSessionInfo, + viewModel = UserSessionOverviewViewModel(session: parameters.session, isCurrentSession: parameters.isCurrentSession) hostingController = VectorHostingController(rootView: UserSessionOverview(viewModel: viewModel.context)) @@ -59,8 +59,8 @@ final class UserSessionOverviewCoordinator: Coordinator, Presentable { switch result { case .verifyCurrentSession: break // TODO: - case let .showSessionDetails(sessionInfo: sessionInfo): - self.completion?(.openSessionDetails(session: sessionInfo)) + case let .showSessionDetails(session: session): + self.completion?(.openSessionDetails(session: session)) } } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift index 00f010d2b..148f36bda 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift @@ -42,21 +42,21 @@ enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { let viewModel: UserSessionOverviewViewModel switch self { case .currentSession: - let currentSessionInfo = UserSessionInfo(sessionId: "session", + let session = UserSessionInfo(sessionId: "session", sessionName: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: Date().timeIntervalSince1970 - 100) - viewModel = UserSessionOverviewViewModel(userSessionInfo: currentSessionInfo, isCurrentSession: true) + viewModel = UserSessionOverviewViewModel(session: session, isCurrentSession: true) case .otherSession: - let currentSessionInfo = UserSessionInfo(sessionId: "session", + let session = UserSessionInfo(sessionId: "session", sessionName: "Mac", deviceType: .desktop, isVerified: true, lastSeenIP: "10.0.0.10", lastSeenTimestamp: Date().timeIntervalSince1970 - 100) - viewModel = UserSessionOverviewViewModel(userSessionInfo: currentSessionInfo, isCurrentSession: false) + viewModel = UserSessionOverviewViewModel(session: session, isCurrentSession: false) } // can simulate service and viewModel actions here if needs be. diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift index f143f292e..2028b4df8 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift @@ -23,7 +23,7 @@ class UserSessionOverviewViewModelTests: XCTestCase { var sut: UserSessionOverviewViewModel! func test_whenVerifyCurrentSessionProcessed_completionWithVerifyCurrentSessionCalled() { - sut = UserSessionOverviewViewModel(userSessionInfo: createUserSessionInfo(), isCurrentSession: true) + sut = UserSessionOverviewViewModel(session: createUserSessionInfo(), isCurrentSession: true) var modelResult: UserSessionOverviewViewModelResult? sut.completion = { result in @@ -35,14 +35,14 @@ class UserSessionOverviewViewModelTests: XCTestCase { func test_whenViewSessionDetailsProcessed_completionWithShowSessionDetailsCalled() { let session = createUserSessionInfo() - sut = UserSessionOverviewViewModel(userSessionInfo: session, isCurrentSession: true) + sut = UserSessionOverviewViewModel(session: session, isCurrentSession: true) var modelResult: UserSessionOverviewViewModelResult? sut.completion = { result in modelResult = result } sut.process(viewAction: .viewSessionDetails) - XCTAssertEqual(modelResult, .showSessionDetails(sessionInfo: session)) + XCTAssertEqual(modelResult, .showSessionDetails(session: session)) } private func createUserSessionInfo() -> UserSessionInfo { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift index e7bf0c271..89b76c7d7 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift @@ -25,7 +25,7 @@ enum UserSessionOverviewCoordinatorResult { // MARK: View model enum UserSessionOverviewViewModelResult: Equatable { - case showSessionDetails(sessionInfo: UserSessionInfo) + case showSessionDetails(session: UserSessionInfo) case verifyCurrentSession } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift index 1b998cad9..034159474 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift @@ -19,14 +19,14 @@ import SwiftUI typealias UserSessionOverviewViewModelType = StateStoreViewModel class UserSessionOverviewViewModel: UserSessionOverviewViewModelType, UserSessionOverviewViewModelProtocol { - private let userSessionInfo: UserSessionInfo + private let session: UserSessionInfo var completion: ((UserSessionOverviewViewModelResult) -> Void)? - init(userSessionInfo: UserSessionInfo, isCurrentSession: Bool) { - self.userSessionInfo = userSessionInfo + init(session: UserSessionInfo, isCurrentSession: Bool) { + self.session = session - let cardViewData = UserSessionCardViewData(userSessionInfo: userSessionInfo, isCurrentSessionDisplayMode: isCurrentSession) + let cardViewData = UserSessionCardViewData(session: session, isCurrentSessionDisplayMode: isCurrentSession) let state = UserSessionOverviewViewState(cardViewData: cardViewData, isCurrentSession: isCurrentSession) super.init(initialViewState: state) } @@ -38,7 +38,7 @@ class UserSessionOverviewViewModel: UserSessionOverviewViewModelType, UserSessio case .verifyCurrentSession: completion?(.verifyCurrentSession) case .viewSessionDetails: - completion?(.showSessionDetails(sessionInfo: userSessionInfo)) + completion?(.showSessionDetails(session: session)) } } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift index 44717d9d0..934c02aff 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift @@ -58,12 +58,12 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { self.showAllInactiveSessions() case .verifyCurrentSession: self.startVerifyCurrentSession() - case let .showCurrentSessionOverview(sessionInfo): - self.showCurrentSessionOverview(sessionInfo: sessionInfo) + case let .showCurrentSessionOverview(session): + self.showCurrentSessionOverview(session: session) case .showAllOtherSessions: self.showAllOtherSessions() - case let .showUserSessionOverview(sessionInfo): - self.showUserSessionOverview(sessionInfo: sessionInfo) + case let .showUserSessionOverview(session): + self.showUserSessionOverview(session: session) } } } @@ -99,12 +99,12 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { // TODO: } - private func showCurrentSessionOverview(sessionInfo: UserSessionInfo) { - completion?(.openSessionOverview(session: sessionInfo, isCurrentSession: true)) + private func showCurrentSessionOverview(session: UserSessionInfo) { + completion?(.openSessionOverview(session: session, isCurrentSession: true)) } - private func showUserSessionOverview(sessionInfo: UserSessionInfo) { - completion?(.openSessionOverview(session: sessionInfo, isCurrentSession: false)) + private func showUserSessionOverview(session: UserSessionInfo) { + completion?(.openSessionOverview(session: session, isCurrentSession: false)) } private func showAllOtherSessions() { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift index 455a4b72a..4fe0f48b3 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift @@ -28,9 +28,9 @@ enum UserSessionsOverviewViewModelResult { case showAllUnverifiedSessions case showAllInactiveSessions case verifyCurrentSession - case showCurrentSessionOverview(sessionInfo: UserSessionInfo) + case showCurrentSessionOverview(session: UserSessionInfo) case showAllOtherSessions - case showUserSessionOverview(sessionInfo: UserSessionInfo) + case showUserSessionOverview(session: UserSessionInfo) } // MARK: View diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift index 47b5e7ad5..c5b1c281b 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift @@ -44,7 +44,7 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess assertionFailure("Missing current session") return } - completion?(.showCurrentSessionOverview(sessionInfo: currentSessionInfo)) + completion?(.showCurrentSessionOverview(session: currentSessionInfo)) case .viewAllUnverifiedSessions: completion?(.showAllUnverifiedSessions) case .viewAllInactiveSessions: @@ -52,11 +52,11 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess case .viewAllOtherSessions: completion?(.showAllOtherSessions) case .tapUserSession(let sessionId): - guard let sessionInfo = userSessionsOverviewService.sessionForIdentifier(sessionId) else { + guard let session = userSessionsOverviewService.sessionForIdentifier(sessionId) else { assertionFailure("Missing session info") return } - completion?(.showUserSessionOverview(sessionInfo: sessionInfo)) + completion?(.showUserSessionOverview(session: session)) } } @@ -68,7 +68,7 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess state.otherSessionsViewData = userSessionsViewData.otherSessions.asViewData() if let currentSessionInfo = userSessionsViewData.currentSession { - state.currentSessionViewData = UserSessionCardViewData(userSessionInfo: currentSessionInfo, isCurrentSessionDisplayMode: true) + state.currentSessionViewData = UserSessionCardViewData(session: currentSessionInfo, isCurrentSessionDisplayMode: true) } } @@ -95,6 +95,6 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess private extension Collection where Element == UserSessionInfo { func asViewData() -> [UserSessionListItemViewData] { - map { UserSessionListItemViewData(userSessionInfo: $0) } + map { UserSessionListItemViewData(session: $0) } } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift index 297998f29..51a698541 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift @@ -69,7 +69,7 @@ struct UserSessionListPreview: View { var body: some View { VStack(alignment: .leading, spacing: 0) { ForEach(userSessionsOverviewService.overviewData.otherSessions) { userSessionInfo in - let viewData = UserSessionListItemViewData(userSessionInfo: userSessionInfo) + let viewData = UserSessionListItemViewData(session: userSessionInfo) UserSessionListItem(viewData: viewData, onBackgroundTap: { _ in diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift index 78a5fab53..b72301eb0 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift @@ -68,7 +68,7 @@ struct UserSessionListItemViewData: Identifiable { } extension UserSessionListItemViewData { - init(userSessionInfo: UserSessionInfo) { - self.init(sessionId: userSessionInfo.sessionId, sessionDisplayName: userSessionInfo.sessionName, deviceType: userSessionInfo.deviceType, isVerified: userSessionInfo.isVerified, lastActivityDate: userSessionInfo.lastSeenTimestamp) + init(session: UserSessionInfo) { + self.init(sessionId: session.sessionId, sessionDisplayName: session.sessionName, deviceType: session.deviceType, isVerified: session.isVerified, lastActivityDate: session.lastSeenTimestamp) } } From 3a826ee8305deac003012213acf4c33b0669ccc0 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Tue, 27 Sep 2022 14:06:58 +0300 Subject: [PATCH 064/104] Removed alignment --- .../Modules/UserSessions/Common/View/SeparatorLine.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/SeparatorLine.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/SeparatorLine.swift index eac45ede2..2f56761ef 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/SeparatorLine.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/SeparatorLine.swift @@ -22,7 +22,7 @@ struct SeparatorLine: View { var body: some View { Rectangle() .fill(theme.colors.quinaryContent) - .frame(maxWidth: .infinity, alignment: .trailing) + .frame(maxWidth: .infinity) .frame(height: 1.0) } } From f6e057eeed71f0aa835b08f31fa5232b943e3ba8 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Tue, 27 Sep 2022 14:12:40 +0300 Subject: [PATCH 065/104] Cleanup --- .../MockUserSessionOverviewScreenState.swift | 1 - .../View/UserSessionOverview.swift | 17 ++++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift index 148f36bda..6967bf1a6 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift @@ -33,7 +33,6 @@ enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { /// A list of screen state definitions static var allCases: [MockUserSessionOverviewScreenState] { - // Each of the presence statuses [.currentSession, .otherSession] } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift index b37a2099e..ec0f4dca3 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift @@ -23,14 +23,13 @@ struct UserSessionOverview: View { var body: some View { ScrollView { - UserSessionCardView(viewData: viewModel.viewState.cardViewData, - onVerifyAction: { _ in - viewModel.send(viewAction: .verifyCurrentSession) - }, + UserSessionCardView(viewData: viewModel.viewState.cardViewData, onVerifyAction: { _ in + viewModel.send(viewAction: .verifyCurrentSession) + }, onViewDetailsAction: { _ in - viewModel.send(viewAction: .viewSessionDetails) - }) - .padding(16) + viewModel.send(viewAction: .viewSessionDetails) + }) + .padding(16) SwiftUI.Section { UserSessionOverviewDisclosureCell(title: VectorL10n.userSessionOverviewSessionDetailsButtonTitle, onBackgroundTap: { viewModel.send(viewAction: .viewSessionDetails) @@ -40,8 +39,8 @@ struct UserSessionOverview: View { .background(theme.colors.system.ignoresSafeArea()) .frame(maxHeight: .infinity) .navigationTitle(viewModel.viewState.isCurrentSession ? - VectorL10n.userSessionOverviewCurrentSessionTitle : - VectorL10n.userSessionOverviewSessionTitle) + VectorL10n.userSessionOverviewCurrentSessionTitle : + VectorL10n.userSessionOverviewSessionTitle) } } From d714b95633e9eda92b13e36728ff78349c3280e5 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Tue, 27 Sep 2022 14:51:15 +0300 Subject: [PATCH 066/104] Made UserSessionInfo Equatable --- .../UserSessions/Common/UserSessionInfo.swift | 6 ++++++ .../UserSessionOverviewModels.swift | 13 ------------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift index ba1d18ecd..59bf5608a 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift @@ -71,3 +71,9 @@ struct UserSessionInfo: Identifiable { } } } + +extension UserSessionInfo: Equatable { + static func == (lhs: UserSessionInfo, rhs: UserSessionInfo) -> Bool { + return lhs.id == rhs.id + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift index 89b76c7d7..deddfc31c 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift @@ -29,19 +29,6 @@ enum UserSessionOverviewViewModelResult: Equatable { case verifyCurrentSession } -extension UserSessionOverviewViewModelResult { - static func == (lhs: UserSessionOverviewViewModelResult, rhs: UserSessionOverviewViewModelResult) -> Bool { - switch (lhs, rhs) { - case (.verifyCurrentSession, .verifyCurrentSession): - return true - case (let .showSessionDetails(session), let .showSessionDetails(session2)): - return session.id == session2.id - default: - return false - } - } -} - // MARK: View struct UserSessionOverviewViewState: BindableState { From 1a27a94da844f0fb7c88f803057cb43778858867 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Tue, 27 Sep 2022 15:36:44 +0300 Subject: [PATCH 067/104] Moved isCurrentSession into UserSessionInfo --- .../UserSessions/Common/UserSessionInfo.swift | 9 ++++-- .../Common/View/UserSessionCardView.swift | 5 +-- .../Common/View/UserSessionCardViewData.swift | 4 +-- .../UserSessionsFlowCoordinator.swift | 12 +++---- .../MockUserSessionDetailsScreenState.swift | 6 ++-- .../UserSessionDetailsViewModelTests.swift | 6 ++-- .../UserSessionOverviewCoordinator.swift | 4 +-- .../MockUserSessionOverviewScreenState.swift | 32 ++++++++++--------- .../UserSessionOverviewViewModelTests.swift | 7 ++-- .../UserSessionOverviewViewModel.swift | 6 ++-- .../UserSessionsOverviewCoordinator.swift | 4 +-- .../UserSessionsOverviewService.swift | 32 +++++++++---------- .../MockUserSessionsOverviewService.swift | 8 ++--- .../UserSessionsOverviewModels.swift | 2 +- .../UserSessionsOverviewViewModel.swift | 2 +- 15 files changed, 75 insertions(+), 64 deletions(-) diff --git a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift index 59bf5608a..a63491e5a 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift @@ -48,6 +48,9 @@ struct UserSessionInfo: Identifiable { /// True to indicate that session has been used under `inactiveSessionDurationTreshold` value let isSessionActive: Bool + /// True to indicate that this is current user session + let isCurrentSession: Bool + // MARK: - Setup init(sessionId: String, @@ -55,20 +58,22 @@ struct UserSessionInfo: Identifiable { deviceType: DeviceType, isVerified: Bool, lastSeenIP: String?, - lastSeenTimestamp: TimeInterval?) { + lastSeenTimestamp: TimeInterval?, + isCurrentSession: Bool) { self.sessionId = sessionId self.sessionName = sessionName self.deviceType = deviceType self.isVerified = isVerified self.lastSeenIP = lastSeenIP self.lastSeenTimestamp = lastSeenTimestamp - + if let lastSeenTimestamp = lastSeenTimestamp { let elapsedTime = Date().timeIntervalSince1970 - lastSeenTimestamp isSessionActive = elapsedTime < Self.inactiveSessionDurationTreshold } else { isSessionActive = true } + self.isCurrentSession = isCurrentSession } } diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift index f082a7306..2a2877528 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift @@ -141,9 +141,10 @@ struct UserSessionCardViewPreview: View { deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", - lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + lastSeenTimestamp: Date().timeIntervalSince1970 - 100, + isCurrentSession: isCurrentSessionInfo) - viewData = UserSessionCardViewData(session: session, isCurrentSessionDisplayMode: isCurrentSessionInfo) + viewData = UserSessionCardViewData(session: session) } var body: some View { diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift index dd259fb8f..3fc86306e 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift @@ -66,13 +66,13 @@ struct UserSessionCardViewData { } extension UserSessionCardViewData { - init(session: UserSessionInfo, isCurrentSessionDisplayMode: Bool = false) { + init(session: UserSessionInfo) { self.init(sessionId: session.sessionId, sessionDisplayName: session.sessionName, deviceType: session.deviceType, isVerified: session.isVerified, lastActivityTimestamp: session.lastSeenTimestamp, lastSeenIP: session.lastSeenIP, - isCurrentSessionDisplayMode: isCurrentSessionDisplayMode) + isCurrentSessionDisplayMode: session.isCurrentSession) } } diff --git a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift index 60861b79c..288ce2bf4 100644 --- a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift @@ -53,8 +53,8 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { coordinator.completion = { [weak self] result in guard let self = self else { return } switch result { - case let .openSessionOverview(session: session, isCurrentSession: isCurrentSession): - self.openSessionOverview(session: session, isCurrentSession: isCurrentSession) + case let .openSessionOverview(session: session): + self.openSessionOverview(session: session) } } return coordinator @@ -70,8 +70,8 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { return UserSessionDetailsCoordinator(parameters: parameters) } - private func openSessionOverview(session: UserSessionInfo, isCurrentSession: Bool) { - let coordinator = createUserSessionOverviewCoordinator(session: session, isCurrentSession: isCurrentSession) + private func openSessionOverview(session: UserSessionInfo) { + let coordinator = createUserSessionOverviewCoordinator(session: session) coordinator.completion = { [weak self] result in guard let self = self else { return } switch result { @@ -82,8 +82,8 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { pushScreen(with: coordinator) } - private func createUserSessionOverviewCoordinator(session: UserSessionInfo, isCurrentSession: Bool) -> UserSessionOverviewCoordinator { - let parameters = UserSessionOverviewCoordinatorParameters(session: session, isCurrentSession: isCurrentSession) + private func createUserSessionOverviewCoordinator(session: UserSessionInfo) -> UserSessionOverviewCoordinator { + let parameters = UserSessionOverviewCoordinatorParameters(session: session) return UserSessionOverviewCoordinator(parameters: parameters) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift index 8d0478560..dcb31faef 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift @@ -46,14 +46,16 @@ enum MockUserSessionDetailsScreenState: MockScreenState, CaseIterable { deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", - lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + lastSeenTimestamp: Date().timeIntervalSince1970 - 100, + isCurrentSession: true) case .sessionSectionOnly: session = UserSessionInfo(sessionId: "session", sessionName: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: nil, - lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + lastSeenTimestamp: Date().timeIntervalSince1970 - 100, + isCurrentSession: false) } let viewModel = UserSessionDetailsViewModel(session: session) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift index 32d427faf..20debe96b 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift @@ -89,13 +89,15 @@ class UserSessionDetailsViewModelTests: XCTestCase { deviceType: DeviceType = .mobile, isVerified: Bool = false, lastSeenIP: String?, - lastSeenTimestamp: TimeInterval = Date().timeIntervalSince1970) -> UserSessionInfo { + lastSeenTimestamp: TimeInterval = Date().timeIntervalSince1970, + isCurrentSession: Bool = true) -> UserSessionInfo { UserSessionInfo(sessionId: sessionId, sessionName: sessionName, deviceType: deviceType, isVerified: isVerified, lastSeenIP: lastSeenIP, - lastSeenTimestamp: lastSeenTimestamp) + lastSeenTimestamp: lastSeenTimestamp, + isCurrentSession: isCurrentSession) } private func sessionNameItem(sessionName: String) -> UserSessionDetailsSectionItemViewData { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift index 6f3acf1c5..d1aeae991 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift @@ -19,7 +19,6 @@ import SwiftUI struct UserSessionOverviewCoordinatorParameters { let session: UserSessionInfo - let isCurrentSession: Bool } final class UserSessionOverviewCoordinator: Coordinator, Presentable { @@ -41,8 +40,7 @@ final class UserSessionOverviewCoordinator: Coordinator, Presentable { init(parameters: UserSessionOverviewCoordinatorParameters) { self.parameters = parameters - viewModel = UserSessionOverviewViewModel(session: parameters.session, - isCurrentSession: parameters.isCurrentSession) + viewModel = UserSessionOverviewViewModel(session: parameters.session) hostingController = VectorHostingController(rootView: UserSessionOverview(viewModel: viewModel.context)) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift index 6967bf1a6..4830859d0 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift @@ -30,34 +30,36 @@ enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { var screenType: Any.Type { UserSessionOverview.self } - + /// A list of screen state definitions static var allCases: [MockUserSessionOverviewScreenState] { [.currentSession, .otherSession] } - + /// Generate the view struct for the screen state. var screenView: ([Any], AnyView) { let viewModel: UserSessionOverviewViewModel switch self { case .currentSession: let session = UserSessionInfo(sessionId: "session", - sessionName: "iOS", - deviceType: .mobile, - isVerified: false, - lastSeenIP: "10.0.0.10", - lastSeenTimestamp: Date().timeIntervalSince1970 - 100) - viewModel = UserSessionOverviewViewModel(session: session, isCurrentSession: true) + sessionName: "iOS", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100, + isCurrentSession: true) + viewModel = UserSessionOverviewViewModel(session: session) case .otherSession: let session = UserSessionInfo(sessionId: "session", - sessionName: "Mac", - deviceType: .desktop, - isVerified: true, - lastSeenIP: "10.0.0.10", - lastSeenTimestamp: Date().timeIntervalSince1970 - 100) - viewModel = UserSessionOverviewViewModel(session: session, isCurrentSession: false) + sessionName: "Mac", + deviceType: .desktop, + isVerified: true, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100, + isCurrentSession: false) + viewModel = UserSessionOverviewViewModel(session: session) } - + // can simulate service and viewModel actions here if needs be. return ([viewModel], AnyView(UserSessionOverview(viewModel: viewModel.context))) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift index 2028b4df8..f2498bc56 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift @@ -23,7 +23,7 @@ class UserSessionOverviewViewModelTests: XCTestCase { var sut: UserSessionOverviewViewModel! func test_whenVerifyCurrentSessionProcessed_completionWithVerifyCurrentSessionCalled() { - sut = UserSessionOverviewViewModel(session: createUserSessionInfo(), isCurrentSession: true) + sut = UserSessionOverviewViewModel(session: createUserSessionInfo()) var modelResult: UserSessionOverviewViewModelResult? sut.completion = { result in @@ -35,7 +35,7 @@ class UserSessionOverviewViewModelTests: XCTestCase { func test_whenViewSessionDetailsProcessed_completionWithShowSessionDetailsCalled() { let session = createUserSessionInfo() - sut = UserSessionOverviewViewModel(session: session, isCurrentSession: true) + sut = UserSessionOverviewViewModel(session: session) var modelResult: UserSessionOverviewViewModelResult? sut.completion = { result in @@ -51,6 +51,7 @@ class UserSessionOverviewViewModelTests: XCTestCase { deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", - lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + lastSeenTimestamp: Date().timeIntervalSince1970 - 100, + isCurrentSession: true) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift index 034159474..95261d7c8 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift @@ -23,11 +23,11 @@ class UserSessionOverviewViewModel: UserSessionOverviewViewModelType, UserSessio var completion: ((UserSessionOverviewViewModelResult) -> Void)? - init(session: UserSessionInfo, isCurrentSession: Bool) { + init(session: UserSessionInfo) { self.session = session - let cardViewData = UserSessionCardViewData(session: session, isCurrentSessionDisplayMode: isCurrentSession) - let state = UserSessionOverviewViewState(cardViewData: cardViewData, isCurrentSession: isCurrentSession) + let cardViewData = UserSessionCardViewData(session: session) + let state = UserSessionOverviewViewState(cardViewData: cardViewData, isCurrentSession: session.isCurrentSession) super.init(initialViewState: state) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift index 934c02aff..ac7521dcb 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift @@ -100,11 +100,11 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { } private func showCurrentSessionOverview(session: UserSessionInfo) { - completion?(.openSessionOverview(session: session, isCurrentSession: true)) + completion?(.openSessionOverview(session: session)) } private func showUserSessionOverview(session: UserSessionInfo) { - completion?(.openSessionOverview(session: session, isCurrentSession: false)) + completion?(.openSessionOverview(session: session)) } private func showAllOtherSessions() { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift index aa387325d..da4e30ba8 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift @@ -71,13 +71,13 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { let device = mainAccount.device else { return nil } - return sessionInfo(from: device) + return sessionInfo(from: device, isCurrentSession: true) } private func sessionsOverviewData(from devices: [MXDevice]) -> UserSessionsOverviewData { let allSessions = devices .sorted { $0.lastSeenTs > $1.lastSeenTs } - .map { sessionInfo(from: $0) } + .map { sessionInfo(from: $0, isCurrentSession: $0.deviceId == mxSession.myDeviceId) } var currentSession: UserSessionInfo? var unverifiedSessions: [UserSessionInfo] = [] @@ -85,19 +85,18 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { var otherSessions: [UserSessionInfo] = [] for session in allSessions { - guard session.sessionId != mxSession.myDeviceId else { + if session.isCurrentSession { currentSession = session - continue - } - - otherSessions.append(session) - - if session.isVerified == false { - unverifiedSessions.append(session) - } - - if session.isSessionActive == false { - inactiveSessions.append(session) + } else { + otherSessions.append(session) + + if session.isVerified == false { + unverifiedSessions.append(session) + } + + if session.isSessionActive == false { + inactiveSessions.append(session) + } } } @@ -107,7 +106,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { otherSessions: otherSessions) } - private func sessionInfo(from device: MXDevice) -> UserSessionInfo { + private func sessionInfo(from device: MXDevice, isCurrentSession: Bool) -> UserSessionInfo { let isSessionVerified = deviceInfo(for: device.deviceId)?.trustLevel.isVerified ?? false var lastSeenTs: TimeInterval? @@ -120,7 +119,8 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { deviceType: .unknown, isVerified: isSessionVerified, lastSeenIP: device.lastSeenIp, - lastSeenTimestamp: lastSeenTs) + lastSeenTimestamp: lastSeenTs, + isCurrentSession: isCurrentSession) } private func deviceInfo(for deviceId: String) -> MXDeviceInfo? { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift index a70df0cf5..13167d350 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift @@ -28,16 +28,16 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { } init() { - let currentSessionInfo = UserSessionInfo(sessionId: "alice", sessionName: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: nil) + let currentSessionInfo = UserSessionInfo(sessionId: "alice", sessionName: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: nil, isCurrentSession: true) let unverifiedSessionsInfo: [UserSessionInfo] = [] let inactiveSessionsInfo: [UserSessionInfo] = [] let otherSessionsInfo: [UserSessionInfo] = [ - UserSessionInfo(sessionId: "1", sessionName: "macOS", deviceType: .desktop, isVerified: true, lastSeenIP: "1.0.0.1", lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000), - UserSessionInfo(sessionId: "2", sessionName: "Firefox on Windows", deviceType: .web, isVerified: true, lastSeenIP: "2.0.0.2", lastSeenTimestamp: Date().timeIntervalSince1970 - 100), - UserSessionInfo(sessionId: "3", sessionName: "Android", deviceType: .mobile, isVerified: false, lastSeenIP: "3.0.0.3", lastSeenTimestamp: Date().timeIntervalSince1970 - 10) + UserSessionInfo(sessionId: "1", sessionName: "macOS", deviceType: .desktop, isVerified: true, lastSeenIP: "1.0.0.1", lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000, isCurrentSession: false), + UserSessionInfo(sessionId: "2", sessionName: "Firefox on Windows", deviceType: .web, isVerified: true, lastSeenIP: "2.0.0.2", lastSeenTimestamp: Date().timeIntervalSince1970 - 100, isCurrentSession: false), + UserSessionInfo(sessionId: "3", sessionName: "Android", deviceType: .mobile, isVerified: false, lastSeenIP: "3.0.0.3", lastSeenTimestamp: Date().timeIntervalSince1970 - 10, isCurrentSession: false) ] overviewData = UserSessionsOverviewData(currentSession: currentSessionInfo, diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift index 4fe0f48b3..ce3e56f0a 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift @@ -19,7 +19,7 @@ import Foundation // MARK: - Coordinator enum UserSessionsOverviewCoordinatorResult { - case openSessionOverview(session: UserSessionInfo, isCurrentSession: Bool) + case openSessionOverview(session: UserSessionInfo) } // MARK: View model diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift index c5b1c281b..65fefaf7e 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift @@ -68,7 +68,7 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess state.otherSessionsViewData = userSessionsViewData.otherSessions.asViewData() if let currentSessionInfo = userSessionsViewData.currentSession { - state.currentSessionViewData = UserSessionCardViewData(session: currentSessionInfo, isCurrentSessionDisplayMode: true) + state.currentSessionViewData = UserSessionCardViewData(session: currentSessionInfo) } } From f51b87557ca0636811588c0b32b993047582c918 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Tue, 27 Sep 2022 17:31:40 +0300 Subject: [PATCH 068/104] Link current session to session overview --- .../Extensions/ConditionalViewModifier.swift | 27 +++++++++++++++++++ .../Common/View/UserSessionCardView.swift | 16 +++++------ changelog.d/6770.wip | 0 3 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 RiotSwiftUI/Modules/Common/Extensions/ConditionalViewModifier.swift create mode 100644 changelog.d/6770.wip diff --git a/RiotSwiftUI/Modules/Common/Extensions/ConditionalViewModifier.swift b/RiotSwiftUI/Modules/Common/Extensions/ConditionalViewModifier.swift new file mode 100644 index 000000000..14f5245aa --- /dev/null +++ b/RiotSwiftUI/Modules/Common/Extensions/ConditionalViewModifier.swift @@ -0,0 +1,27 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +extension View { + @ViewBuilder func `if`(_ condition: @autoclosure () -> Bool, transform: (Self) -> Content) -> some View { + if condition() { + transform(self) + } else { + self + } + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift index 2a2877528..460f273b3 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift @@ -112,14 +112,9 @@ struct UserSessionCardView: View { } if viewData.isCurrentSessionDisplayMode { - Button { - onViewDetailsAction?(viewData.sessionId) - } label: { - Text(VectorL10n.userSessionViewDetails) - .font(theme.fonts.body) - .foregroundColor(theme.colors.accent) - } - .padding(.top, 4) + Text(VectorL10n.userSessionViewDetails) + .font(theme.fonts.body) + .foregroundColor(theme.colors.accent) } } .padding(24) @@ -127,6 +122,11 @@ struct UserSessionCardView: View { .background(theme.colors.background) .clipShape(backgroundShape) .shapedBorder(color: theme.colors.quinaryContent, borderWidth: 1.0, shape: backgroundShape) + .if(viewData.isCurrentSessionDisplayMode) { view in + view.onTapGesture { + onViewDetailsAction?(viewData.sessionId) + } + } } } diff --git a/changelog.d/6770.wip b/changelog.d/6770.wip new file mode 100644 index 000000000..e69de29bb From ef7f598f2caeec1101357219900ec852303c34eb Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Tue, 27 Sep 2022 17:50:02 +0300 Subject: [PATCH 069/104] Changelog --- changelog.d/6770.wip | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.d/6770.wip b/changelog.d/6770.wip index e69de29bb..89911c57c 100644 --- a/changelog.d/6770.wip +++ b/changelog.d/6770.wip @@ -0,0 +1 @@ +Device Manager: Link current session to session overview. From c5b895c5a0f4454169304756945bc8c4132ecf95 Mon Sep 17 00:00:00 2001 From: Vri Date: Mon, 26 Sep 2022 18:01:51 +0000 Subject: [PATCH 070/104] Translated using Weblate (German) Currently translated at 100.0% (2198 of 2198 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ --- Riot/Assets/de.lproj/Vector.strings | 150 +++++++++++++++++----------- 1 file changed, 90 insertions(+), 60 deletions(-) diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 569d7a78d..3d8567824 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -64,16 +64,16 @@ "auth_reset_password_next_step_button" = "Ich habe meine E-Mail-Adresse verifiziert"; "auth_reset_password_email_validation_message" = "Eine E-Mail wurde an %@ gesendet. Wenn du dem Link dort gefolgt bist, klicke unten."; // Chat creation -"room_creation_title" = "Neuer Chat"; +"room_creation_title" = "Neue Unterhaltung"; "room_creation_account" = "Konto"; "room_creation_appearance" = "Aussehen"; "room_creation_appearance_name" = "Name"; "room_creation_appearance_picture" = "Avatar (optional)"; "room_creation_privacy" = "Datenschutz"; -"room_creation_private_room" = "Dieser Chat ist privat"; -"room_creation_public_room" = "Dieser Chat ist öffentlich"; +"room_creation_private_room" = "Diese Unterhaltung ist privat"; +"room_creation_public_room" = "Diese Unterhaltung ist öffentlich"; "room_creation_make_public" = "Mache öffentlich"; -"room_creation_make_public_prompt_title" = "Diesen Chat öffentlich machen?"; +"room_creation_make_public_prompt_title" = "Diese Unterhaltung öffentlich machen?"; "room_creation_keep_private" = "Privat lassen"; "room_creation_make_private" = "Mache privat"; "room_creation_wait_for_creation" = "Es wird gerade schon ein Raum erstellt. Bitte warten."; @@ -85,7 +85,7 @@ "room_recents_no_conversation" = "Keine Räume"; "room_recents_low_priority_section" = "NIEDRIGE PRIORITÄT"; "room_recents_invites_section" = "EINLADUNGEN"; -"room_recents_start_chat_with" = "Starte Chat"; +"room_recents_start_chat_with" = "Unterhaltung beginnen"; "room_recents_create_empty_room" = "Erstelle Raum"; "room_recents_join_room" = "Tritt Raum bei"; "room_recents_join_room_title" = "Trete einem Raum bei"; @@ -121,9 +121,9 @@ "room_participants_leave_prompt_title" = "Raum verlassen"; "room_participants_leave_prompt_msg" = "Bist du sicher, dass du den Raum verlassen willst?"; "room_participants_remove_prompt_title" = "Bestätigung"; -"room_participants_remove_prompt_msg" = "Bist du sicher, dass du %@ aus dem Chat entfernen möchtest?"; +"room_participants_remove_prompt_msg" = "Bist du sicher, dass du %@ aus der Unterhaltung entfernen möchtest?"; "room_participants_invite_prompt_title" = "Bestätigung"; -"room_participants_invite_prompt_msg" = "Bist du sicher, dass du %@ in diesen Chat einladen möchtest?"; +"room_participants_invite_prompt_msg" = "Bist du sicher, dass du %@ in diese Unterhaltung einladen möchtest?"; "room_participants_filter_room_members" = "Mitglieder filtern"; "room_participants_invite_malformed_id_title" = "Fehler beim Einladen"; "room_participants_invited_section" = "EINGELADEN"; @@ -131,7 +131,7 @@ "room_participants_offline" = "Offline"; "room_participants_unknown" = "Unbekannt"; "room_participants_now" = "jetzt"; -"room_participants_action_section_direct_chats" = "Direkte Chats"; +"room_participants_action_section_direct_chats" = "Direktnachrichten"; "room_participants_action_section_devices" = "Sitzungen"; "room_participants_action_section_other" = "Optionen"; "room_participants_action_invite" = "Einladen"; @@ -142,7 +142,7 @@ "room_participants_action_unignore" = "Alle Nachrichten dieser Person zeigen"; "room_participants_action_set_moderator" = "Moderationsrechte vergeben"; "room_participants_action_set_admin" = "Administrationsrechte vergeben"; -"room_participants_action_start_new_chat" = "Starte neuen Chat"; +"room_participants_action_start_new_chat" = "Unterhaltung beginnen"; "room_participants_action_start_video_call" = "Starte Video-Anruf"; "room_participants_action_mention" = "Erwähnen"; // Chat @@ -239,7 +239,7 @@ "settings_new_password" = "Neues Passwort"; "settings_confirm_password" = "Bestätige Passwort"; "settings_password_updated" = "Das Passwort deines Matrix-Kontos wurde aktualisiert"; -"settings_crypto_device_name" = "Öffentlicher Sitzungs-Name: "; +"settings_crypto_device_name" = "Sitzungsname: "; "settings_crypto_device_key" = "\nSitzungs-Schlüssel:\n"; "settings_crypto_export" = "Schlüssel exportieren"; "room_details_settings" = "Einstellungen"; @@ -303,7 +303,7 @@ "room_participants_action_start_voice_call" = "Starte Sprach-Anruf"; "room_ongoing_conference_call" = "Laufender Konferenz-Anruf. Trete bei als %@ oder %@."; "room_event_action_redact" = "Entfernen"; -"room_warning_about_encryption" = "Ende-zu-Ende Verschlüsselung ist in Beta und ist evtl. nicht zuverlässig.\n\nMan sollte noch nicht darauf vertrauen, dass die Daten sicher sind.\n\nGeräte werden Nachrichten von vor dem Beitritt des Raumes nicht entschlüsseln können.\n\nVerschlüsselte Nachrichten sind nicht lesbar auf Clients, die die Verschlüsselung noch nicht implementiert haben."; +"room_warning_about_encryption" = "Ende-zu-Ende-Verschlüsselung ist in Beta und ist evtl. nicht zuverlässig.\n\nMan sollte noch nicht darauf vertrauen, dass die Daten sicher sind.\n\nGeräte werden Nachrichten von vor dem Beitritt des Raumes nicht entschlüsseln können.\n\nVerschlüsselte Nachrichten sind nicht lesbar in Anwendungen, die die Verschlüsselung noch nicht implementiert haben."; "unknown_devices_alert" = "Dieser Raum enthält unbekannte Sitzungen, die nicht verifiziert wurden.\nDas bedeutet, es gibt keine Garantie, dass sie den angegebenen Benutzern gehört.\nWir empfehlen eine Überprüfung für jedes Gerät, bevor du weitermachst. Du kannst die Nachricht auch ohne Verifizierung erneut senden."; "room_preview_unlinked_email_warning" = "Diese Einladung wurde an %@ gesendet, welche diesem Konto nicht zugeordnet ist. Möglicherweise möchtest du dich mit einem anderen Konto anmelden oder diese E-Mail zu deinem Konto hinzufügen."; "room_preview_try_join_an_unknown_room" = "Du versuchst auf %@ zuzugreifen. Möchtest du dem Raum beitreten um teilzunehmen?"; @@ -313,12 +313,12 @@ "settings_phone_number" = "Telefon"; "settings_add_phone_number" = "Telefonnummer hinzufügen"; "settings_fail_to_update_profile" = "Profilaktualisierung fehlgeschlagen"; -"settings_global_settings_info" = "Globale Benachrichtigungseinstellungen sind auf deinem %@ web-Client verfügbar"; +"settings_global_settings_info" = "Globale Benachrichtigungseinstellungen sind in deiner %@ Web-Anwendung verfügbar"; "settings_pin_rooms_with_missed_notif" = "Pinnen von Räumen mit verpassten Benachrichtigungen"; "settings_pin_rooms_with_unread" = "Pinnen von Räumen mit ungelesenen Nachrichten"; "settings_contacts_discover_matrix_users" = "Entdecke andere Benutzer mittels E-Mail-Adressen oder Telefonnummern"; "settings_labs_e2e_encryption_prompt_message" = "Zum Fertigstellen der Verschlüsselung bitte neu anmelden."; -"settings_third_party_notices" = "Anmerkungen von Dritten"; +"settings_third_party_notices" = "Drittanbieter-Lizenzen"; "settings_clear_cache" = "Cache löschen"; "settings_fail_to_update_password" = "Passwortänderung des Matrix-Kontos fehlgeschlagen"; "settings_crypto_device_id" = "\nSitzungs-ID: "; @@ -391,7 +391,7 @@ "bug_report_description" = "Beschreibe den Fehler. Was hast du gemacht? Was hast du erwartet? Was ist tatsächlich passiert?"; "bug_crash_report_title" = "Absturzmeldung"; "bug_crash_report_description" = "Bitte beschreibe was du vor dem Absturz gemacht hast:"; -"bug_report_logs_description" = "Um Probleme analysieren zu können werden Protokolldateien dieses Clients mit dem Fehlerbericht gesendet. Wenn du nur den Text oben senden willst, bitte abwählen:"; +"bug_report_logs_description" = "Um Probleme analysieren zu können, werden Protokolldateien dieser Anwendung mit dem Fehlerbericht gesendet. Wenn du nur den Text oben senden willst, bitte abwählen:"; "bug_report_send_logs" = "Sende Protokolldateien"; "bug_report_send_screenshot" = "Sende Bildschirmfoto"; "bug_report_progress_zipping" = "Sammele Protokolle"; @@ -427,7 +427,7 @@ "send_to" = "Sende an %@"; "sending" = "Senden"; "room_event_failed_to_send" = "Senden fehlgeschlagen"; -"room_details_direct_chat" = "Direkter Chat"; +"room_details_direct_chat" = "Direktnachrichten"; "room_details_fail_to_update_room_direct" = "Setzen der Direktkennzeichnung in diesem Raum fehlgeschlagen"; "event_formatter_widget_added" = "Widget %@ wurde von %@ hinzugefügt"; "event_formatter_widget_removed" = "Widget %@ wurde von %@ entfernt"; @@ -610,14 +610,14 @@ "key_backup_recover_invalid_passphrase" = "Sicherung konnte mit dieser Phrase nicht entschlüsselt werden: Bitte stelle sicher, dass du die richtige Sicherungsphrase eingegeben hast."; "key_backup_recover_invalid_recovery_key_title" = "Ungültiger Sicherungsschlüssel"; "key_backup_recover_invalid_recovery_key" = "Sicherung konnte mit diesem Schlüssel nicht entschlüsselt werden: Bitte stelle sicher, dass du den richtigen Sicherungsschlüssel eingegeben hast."; -"key_backup_recover_from_passphrase_info" = "Nutze deine Sicherungsphrase um deinen sicheren Chatverlauf zu entschlüsseln"; +"key_backup_recover_from_passphrase_info" = "Nutze deine Sicherungsphrase, um deinen sicheren Nachrichtenverlauf zu entschlüsseln"; "key_backup_recover_from_passphrase_passphrase_title" = "Eingeben"; "key_backup_recover_from_passphrase_passphrase_placeholder" = "Phrase eingeben"; "key_backup_recover_from_passphrase_recover_action" = "Historie entschlüsseln"; "key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Kennst deine Sicherungsphrase nicht? Du kannst "; "key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "deinen Sicherungsschlüssel nutzen"; "key_backup_recover_from_passphrase_lost_passphrase_action_part3" = "."; -"key_backup_recover_from_recovery_key_info" = "Nutze deinen Sicherungsschlüssel, um deine Chatverläufe zu entschlüsseln"; +"key_backup_recover_from_recovery_key_info" = "Nutze deinen Sicherungsschlüssel, um deinen Nachrichtenverlauf zu entschlüsseln"; "key_backup_recover_from_recovery_key_recovery_key_title" = "Eingeben"; "key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Sicherungsschlüssel eingeben"; "key_backup_recover_from_recovery_key_recover_action" = "Verlauf entschlüsseln"; @@ -649,7 +649,7 @@ "key_backup_setup_intro_manual_export_action" = "Manueller Schlüssel-Export"; // String for App Store "store_short_description" = "Sicherer, dezentralisierter Chat/VoIP"; -"store_full_description" = "Element ist die neue Art von Messenger- und Kooperations-App, die:\n\n1. dir die Kontrolle gibt deine Privatsphäre zu schützen,\n2. dir die Kommunikation mit anderen Personen im Matrix-Netzwerk und darüber hinaus Integration in Apps wie Slack ermöglicht,\n3. dich vor Werbung, Datenerfassung, Hintertüren und geschlossene Plattformen schützt,\n4. dich durch End-to-End-Verschlüsselung absichert und mit Quersignaturen andere überprüft.\n\nElement unterscheidet sich grundlegend von anderen Messaging- und Kooperations-Diensten, da es dezentralisiert und open-source ist.\n\nMit Element kannst du dich selbst hosten oder einen Host auswählen, sodass du Datenschutz, Eigentum und Kontrolle über deine Daten und Konversationen hast. Du erhältst Zugriff auf ein offenes Netzwerk und bist nicht auf Element-Nutzer beschränkt. Und es ist sehr sicher.\n\nElement ist in der Lage, all dies zu tun, da es mit Matrix arbeitet - dem Standard für offene, dezentrale Kommunikation.\n\nMit Element hast du die Kontrolle, indem du auswählen kannst, wer deine Konversationen hostet. In der Element-App kannst du verschiedene Hosts auswählen:\n\n1. Hole dir ein kostenloses Konto auf dem öffentlichen Server von matrix.org\n2. Hoste dein Konto selbst, indem du einen Server auf deiner eigenen Hardware ausführst\n3. Melde dich für ein Konto auf einem benutzerdefinierten Server an, indem du einfach die Hosting-Plattform von Element Matrix Services abonnierst\n\nWarum Element?\n\nBESITZE DEINE DATEN: Du entscheidest, wo deine Daten und Nachrichten aufbewahrt werden sollen. Du besitzt und kontrollierst sie, nicht irgendein MEGAKONZERN, die deine Daten verwertet oder Dritten Zugriff gewährt.\n\nOPEN MESSAGING UND ZUSAMMENARBEIT: Du kannst mit allen anderen Mitgliedern des Matrix-Netzwerks chatten, unabhängig davon, ob sie Element oder eine andere Matrix-App verwenden, sogar selbst wenn sie ein anderes Messaging-System wie Slack, IRC oder XMPP verwenden.\n\nSUPERSICHER: Echte End-to-End-Verschlüsselung (nur diejenigen in der Konversation können Nachrichten entschlüsseln) und Quersignierung, um die Geräte der Konversationsteilnehmer zu überprüfen.\n\nVOLLSTÄNDIGE KOMMUNIKATION: Messaging, Sprach- und Videoanrufe, Dateifreigabe, Bildschirmfreigabe und eine ganze Reihe von Integrationen, Bots und Widgets. Baue Räume, Gemeinschaften, bleib in Kontakt und erledige Dinge.\n\nÜBERALL, WO DU BIST: Bleibe mit dem vollständig synchronisierten Nachrichtenverlauf auf all deinen Geräten und im Internet (unter https://element.io/app) unabhängig voneinander in Kontakt."; +"store_full_description" = "Element ist die neue Art von Kommunikations- und Kooperations-App, die:\n\n1. dir die Kontrolle gibt, deine Privatsphäre zu schützen\n2. dir die Kommunikation mit anderen Personen im Matrix-Netzwerk und darüber hinaus Integration in Apps wie Slack ermöglicht\n3. dich vor Werbung, Datenerfassung, Hintertüren und geschlossene Plattformen schützt\n4. dich durch Ende-zu-Ende-Verschlüsselung absichert und mit Quersignaturen andere überprüft\n\nElement unterscheidet sich grundlegend von anderen Kommunikations- und Kooperations-Diensten, da es dezentralisiert und Open-Source ist.\n\nElement lässt dir die Wahl, ob du einen eigenen Server betreibst oder einen bestehenden wählst, sodass du Datenschutz, Eigentum und Kontrolle über deine Daten und Konversationen hast. Du erhältst Zugriff auf ein offenes Netzwerk und bist nicht auf Element-Nutzer beschränkt. Und es ist sehr sicher.\n\nElement ist in der Lage, all dies zu tun, da es mit Matrix arbeitet – dem Standard für offene, dezentrale Kommunikation.\n\nMit Element hast du die Kontrolle, indem du auswählen kannst, bei wem deine Unterhaltungen liegen. In der Element-App kannst du verschiedene Betreiber auswählen:\n\n1. Hole dir ein kostenloses Konto auf dem öffentlichen Server von matrix.org\n2. Beherberge dein Konto selbst, indem du einen Server auf deiner eigenen Hardware betreibst\n3. Registriere ein Konto auf einem maßgeschneiderten Server, indem du einfach die Element-Matrix-Services abonnierst\n\nWarum Element?\n\nBESITZE DEINE DATEN: Du entscheidest, wo deine Daten und Nachrichten aufbewahrt werden sollen. Du besitzt und kontrollierst sie, nicht irgendein MEGAKONZERN, der deine Daten verwertet oder dritten Zugriff gewährt.\n\nOFFENE KOMMUNIKATION UND ZUSAMMENARBEIT: Du kannst mit allen anderen Mitgliedern des Matrix-Netzwerks schreiben, unabhängig davon, ob sie Element oder eine andere Matrix-App verwenden, selbst wenn sie eine andere Plattform wie beispielsweise Slack, IRC oder XMPP verwenden.\n\nSUPER SICHER: Echte Ende-zu-Ende-Verschlüsselung (nur diejenigen in der Konversation können Nachrichten entschlüsseln) und Quersignierung, um die Geräte der Konversationsteilnehmer zu überprüfen.\n\nVOLLSTÄNDIGE KOMMUNIKATION: Schreiben, Sprach- und Videoanrufe, Dateifreigabe, Bildschirmfreigabe und eine ganze Reihe von Integrationen, Bots und Widgets. Erschaffe Räume, Gemeinschaften, bleib in Kontakt und erledige Dinge.\n\nÜBERALL, WO DU BIST: Bleibe mit dem vollständig synchronisierten Nachrichtenverlauf auf all deinen Geräten und im Internet (unter https://element.io/app) unabhängig voneinander in Kontakt."; "auth_login_single_sign_on" = "Anmelden"; "auth_autodiscover_invalid_response" = "Ungültige Antwort beim Entdecken des Heimservers"; "room_message_unable_open_link_error_message" = "Konnte Link nicht öffnen."; @@ -751,7 +751,7 @@ // MARK: Start "device_verification_start_title" = "Verifizieren durch Vergleichen eines kurzen Textes"; "device_verification_start_wait_partner" = "Warten auf Annahme durch Partner…"; -"device_verification_start_use_legacy" = "Passiert nichts? Noch nicht alle Clients unterstützen die interaktive Verifizierung. Verwende die alte Verifizierungsmethode."; +"device_verification_start_use_legacy" = "Passiert nichts? Noch nicht alle Anwendungen unterstützen die interaktive Verifizierung. Verwende die alte Verifizierungsmethode."; "device_verification_start_use_legacy_action" = "Verwende alte Verifizierungsmethode"; // MARK: Verify "device_verification_verify_title_emoji" = "Überprüfe diese Sitzung indem du bestätigst, dass das folgende Emoji auf dem Bildschirm des Gegenüber angezeigt wird"; @@ -797,7 +797,7 @@ "photo_library_access_not_granted" = "%@ hat keine Berechtigung zum Zugriff auf die Fotobibliothek. Bitte ändere die Datenschutzeinstellungen"; "auth_forgot_password_error_no_configured_identity_server" = "Es ist kein Identitätsserver konfiguriert: Füge einen hinzu, um das Kennwort deines Matrix-Kontos zurückzusetzen."; "room_creation_error_invite_user_by_email_without_identity_server" = "Es ist kein Identitätsserver konfiguriert, sodass du keine Teilnehmenden mit einer E-Mail hinzufügen kannst."; -"room_participants_start_new_chat_error_using_user_email_without_identity_server" = "Es ist kein Identitätsserver konfiguriert, sodass du keinen Chat mit einem Kontakt über eine E-Mail starten kannst."; +"room_participants_start_new_chat_error_using_user_email_without_identity_server" = "Da kein Identitätsserver konfiguriert wurde, kannst du keine Unterhaltung mittels E-Mail-Adresse beginnen."; // Service terms "service_terms_modal_title" = "Nutzungsbedingungen"; "service_terms_modal_accept_button" = "Akzeptieren"; @@ -1003,7 +1003,7 @@ "security_settings_secure_backup_delete" = "Backup löschen"; "security_settings_crosssigning_info_ok" = "Quersignierung ist bereit zur Anwendung."; "security_settings_crosssigning_reset" = "Zurücksetzen"; -"security_settings_coming_soon" = "Entschuldigung, diese Funktion ist noch nicht für %@ iOS verfügbar. Bitte nutze einen anderen Matrix-Client, um es einzurichten. %@ iOS wird es benutzen."; +"security_settings_coming_soon" = "Entschuldigung, diese Funktion ist noch nicht für %@ iOS verfügbar. Bitte nutze eine andere Matrix-Anwendung, um es einzurichten. %@ iOS wird es benutzen."; "security_settings_user_password_description" = "Bestätige deine Identität durch Eingabe des Passworts deines Matrix-Kontos"; // AuthenticatedSessionViewControllerFactory "authenticated_session_flow_not_supported" = "Diese App unterstützt nicht diese Authentifizierungsmethode für deinen Heimserver."; @@ -1051,7 +1051,7 @@ "key_verification_self_verify_unverified_sessions_alert_message" = "Verifiziere alle deine Sitzungen, um sicher zu stellen, dass dein Konto und deine Nachrichten sicher sind."; "key_verification_self_verify_unverified_sessions_alert_validate_action" = "Überprüfung"; "device_verification_self_verify_wait_new_sign_in_title" = "Diese Anmeldung verifizieren"; -"device_verification_self_verify_wait_additional_information" = "Dies funktioniert mit %@ oder einem anderen Matrix-Client, der Quersignierung unterstützt."; +"device_verification_self_verify_wait_additional_information" = "Dies funktioniert mit %@ oder einer anderen Matrix-Anwendung, die Quersignierung unterstützt."; "device_verification_self_verify_wait_recover_secrets_without_passphrase" = "Sicherungsschlüssel verwenden"; "device_verification_self_verify_wait_recover_secrets_with_passphrase" = "Sicherungsphrase oder -schlüssel verwenden"; "device_verification_self_verify_wait_recover_secrets_additional_information" = "Falls du keinen Zugang zu einer existierenden Sitzung hast"; @@ -1062,7 +1062,7 @@ "key_verification_verify_sas_additional_information" = "Für ultimative Sicherheit verwende ein anderes vertrauenswürdiges Kommunikationsmittel oder mache es persönlich."; "key_verification_manually_verify_device_title" = "Verifiziere manuell mit einem Text"; "key_verification_manually_verify_device_instruction" = "Vergleiche die folgenden Zeichen mit den Einstellungen in der Sitzung des anderen Nutzers und bestätige:"; -"key_verification_manually_verify_device_name_title" = "Name der Sitzung"; +"key_verification_manually_verify_device_name_title" = "Sitzungsname"; "key_verification_manually_verify_device_id_title" = "Sitzungs-ID"; "key_verification_manually_verify_device_key_title" = "Sitzungsschlüssel"; "key_verification_manually_verify_device_additional_information" = "Falls sie nicht übereinstimmen, wurde die Kommunikation vielleicht kompromittiert."; @@ -1178,19 +1178,19 @@ "searchable_directory_x_network" = "%@ Netzwerk"; "searchable_directory_search_placeholder" = "Name oder ID"; "create_room_title" = "Neuer Raum"; -"create_room_section_header_name" = "Raumname"; +"create_room_section_header_name" = "NAME"; "create_room_placeholder_name" = "Name"; "create_room_section_header_topic" = "THEMA (OPTIONAL)"; "create_room_placeholder_topic" = "Um was geht es in diesem Raum?"; -"create_room_section_header_encryption" = "Raumverschlüsselung"; +"create_room_section_header_encryption" = "VERSCHLÜSSELUNG"; "create_room_enable_encryption" = "Verschlüsselung aktivieren"; "create_room_section_footer_encryption" = "Verschlüsselung kann im Nachhinein nicht deaktiviert werden."; -"create_room_section_header_type" = "Raumtyp"; +"create_room_section_header_type" = "BEITRITTSBERECHTIGTE"; "create_room_type_private" = "Privater Raum (nur Eingeladene)"; "create_room_type_public" = "Öffentlicher Raum (jeder hat Zugriff)"; "create_room_section_footer_type" = "Personen können einen privaten Raum nur mit Einladung betreten."; "create_room_show_in_directory" = "Im Raumverzeichnis zeigen"; -"create_room_section_header_address" = "Raum-Adresse"; +"create_room_section_header_address" = "ADRESSE"; "create_room_placeholder_address" = "#testraum:matrix.org"; "room_info_list_room_encrypted" = "Nachrichten in diesem Raum sind Ende-zu-Ende verschlüsselt"; "room_info_list_one_member" = "1 Mitglied"; @@ -1241,9 +1241,9 @@ // MARK: - Favourites "favourites_empty_view_title" = "Favorisierte Räume und Personen"; -"favourites_empty_view_information" = "Um einen Lieblingschat hinzuzufügen, halte diesen gedrückt und wähle den Stern aus. Du findest deine Lieblingschats gesammelt hier."; -"home_empty_view_information" = "Die sichere Chat-App für Teams, Freunde und Organisationen. Tippe unten auf das +, um Leute und Räume hinzuzufügen."; -"rooms_empty_view_information" = "Noch keine Chat-Räume vorhanden. Tippe auf das +, um einem Raum beizutreten oder einen neuen zu erstellen."; +"favourites_empty_view_information" = "Du kannst Unterhaltungen favorisieren – am schnellsten geht das, indem du sie gedrückt hältst. Tippe auf den Stern und schon werden sie hier angezeigt."; +"home_empty_view_information" = "Die sichere Kommunikations-App für Gruppen, Freunde und Organisationen. Tippe unten auf das +, um Personen und Räume hinzuzufügen."; +"rooms_empty_view_information" = "Räume sind großartig für Gruppenunterhaltungen, ob privat oder öffentlich. Tippe auf +, bestehende Räume zu finden oder neue zu erstellen."; "invite_friends_share_text" = "Hey! Sprich mit mir auf %@: %@"; // MARK: - Invite friends @@ -1318,7 +1318,7 @@ "room_accessibility_video_call" = "Videoanruf"; "room_message_replying_to" = "%@ anworten"; "room_message_editing" = "Bearbeitung"; -"space_beta_announce_information" = "Spaces bieten neue Möglichkeiten um Räume und Personen zu gruppieren. Sie sind noch nicht auf iOS verfügbar, aber du kannst sie jetzt schon mit Web und Desktop nutzen."; +"space_beta_announce_information" = "Spaces sind eine neue Möglichkeit, Räume und Personen zu gruppieren. Sie sind noch nicht auf iOS verfügbar, aber du kannst sie jetzt schon mit Web und Desktop nutzen."; "space_feature_unavailable_information" = "Spaces bieten neue Möglichkeiten um Räume und Personen zu gruppieren.\n\nBald werden sie auch hier verfügbar sein. Für den Moment kannst du ihnen auf einer der anderen Plattformen beitreten und hier auf alle Räume zugreifen, denen du dort beitrittst."; "space_beta_announce_subtitle" = "Die verbesserte Version von Communities"; "space_beta_announce_title" = "Spaces sind bald verfügbar"; @@ -1423,8 +1423,8 @@ "settings_messages_containing_at_room" = "@room"; "settings_messages_containing_user_name" = "Meinem Benutzernamen"; "settings_messages_containing_display_name" = "Meinem Anzeigenamen"; -"settings_encrypted_group_messages" = "Verschlüsselten Gruppenchats"; -"settings_group_messages" = "Gruppenchats"; +"settings_encrypted_group_messages" = "Verschlüsselte Gruppennachrichten"; +"settings_group_messages" = "Gruppennachrichten"; "settings_encrypted_direct_messages" = "Verschlüsselten Direktnachrichten"; "settings_direct_messages" = "Direktnachrichten"; "settings_notify_me_for" = "Benachrichtige mich bei"; @@ -1500,7 +1500,7 @@ "contacts_address_book_permission_denied_alert_title" = "Kontakte deaktiviert"; "poll_edit_form_create_options" = "Erstelle Optionen"; "settings_discovery_accept_terms" = "Bedingungen des Identitätsservers akzeptieren"; -"find_your_contacts_message" = "Lass dir von %@ deine Kontakte anzeigen um schnell mit denen zu chatten, die du am besten kennst."; +"find_your_contacts_message" = "Lass dir von %@ deine Kontakte anzeigen, um schnell mit denen zu schreiben, die du am besten kennst."; "poll_timeline_votes_count" = "%lu Stimmen"; "poll_timeline_one_vote" = "1 Stimme"; "poll_edit_form_add_option" = "Option hinzufügen"; @@ -1627,7 +1627,7 @@ "room_event_action_reply_in_thread" = "Thread"; "room_event_action_view_in_room" = "Im Raum anzeigen"; "location_sharing_open_open_street_maps" = "In OpenStreetMap öffnen"; -"onboarding_use_case_title" = "Mit wem wirst du am meisten chatten?"; +"onboarding_use_case_title" = "Mit wem wirst du am meisten schreiben?"; "onboarding_use_case_message" = "Wir helfen dir, dich zu verbinden"; "onboarding_use_case_personal_messaging" = "Freunde und Familie"; "onboarding_use_case_work_messaging" = "Teams"; @@ -1694,7 +1694,7 @@ "set_default_power_level" = "Berechtigungsstufe zurücksetzen"; "set_moderator" = "Moderator setzen"; "set_admin" = "Administrator setzen"; -"start_chat" = "Chat starten"; +"start_chat" = "Unterhaltung beginnen"; "start_voice_call" = "Sprachanruf starten"; "start_video_call" = "Videoanruf starten"; "mention" = "Erwähnung"; @@ -1872,7 +1872,7 @@ "attachment_cancel_upload" = "Hochladen abbrechen?"; "attachment_multiselection_size_prompt" = "Bilder senden als:"; "attachment_multiselection_original" = "Originalgröße"; -"attachment_e2e_keys_file_prompt" = "Diese Datei enthält von einem Matrixclient exportierte Schlüssel.\nMöchtest du den Dateiinhalt sehen oder die Schlüssel importieren?"; +"attachment_e2e_keys_file_prompt" = "Diese Datei enthält von einer Matrix-Anwendung exportierte Schlüssel.\nMöchtest du den Dateiinhalt sehen oder die Schlüssel importieren?"; "attachment_e2e_keys_import" = "Importiere..."; // Contacts "contact_mx_users" = "Matrixbenutzer"; @@ -1888,12 +1888,12 @@ "format_time_d" = "t"; // E2E import "e2e_import_room_keys" = "Importiere Raumschlüssel"; -"e2e_import_prompt" = "Dieser Prozess erlaubt es dir, Schlüssel zu importieren, die du vorher von einem anderen Matrixclient exportiert hast. Du kannst anschließend alle Nachrichten entschlüsseln, die auch bereits der andere Client entschlüsseln konnte.\nDie Exportdatei ist mit einer Passphrase geschützt. Gib die Passphrase hier ein, um die Datei zu importieren."; +"e2e_import_prompt" = "Dieser Prozess erlaubt es dir, Schlüssel zu importieren, die du vorher von einer anderen Matrix-Anwendung exportiert hast. Du kannst anschließend alle Nachrichten entschlüsseln, die auch bereits die andere Anwendung entschlüsseln konnte.\nDie Exportdatei ist mit einer Passphrase geschützt. Gib die Passphrase hier ein, um die Datei zu importieren."; "e2e_import" = "Importieren"; "e2e_passphrase_enter" = "Passphrase eingeben"; // E2E export "e2e_export_room_keys" = "Exportiere Raumschlüssel"; -"e2e_export_prompt" = "Dieser Prozess erlaubt den Export von Schlüsseln, die du in verschlüsselten Räumen empfangen hast, in eine lokale Datei. Du kannst dann die Datei in einem anderen Matrixclient in Zukunft importieren, so dass dieser Client die Nachrichten auch entschlüsseln kann.\nDie exportierte Datei wird jedem der sie lesen kann erlauben, alle verschlüsselten Nachrichten sehen können, also verwahre die Datei sicher."; +"e2e_export_prompt" = "Dieser Prozess erlaubt den Export von Schlüsseln, die du in verschlüsselten Räumen empfangen hast, in eine lokale Datei. Du kannst dann die Datei in einer anderen Matrix-Anwendung importieren, sodass diese die Nachrichten auch entschlüsseln kann.\nDie exportierte Datei wird jedem, der sie lesen kann, erlauben, alle verschlüsselten Nachrichten sehen können, also verwahre die Datei sicher."; "e2e_export" = "Exportiere"; "e2e_passphrase_confirm" = "Passphrase bestätigen"; "e2e_passphrase_empty" = "Die Passphrase darf nicht leer sein"; @@ -2025,7 +2025,7 @@ "membership_ban" = "Gesperrt"; "num_members_one" = "%@ Benutzer"; "num_members_other" = "%@ Benutzer"; -"kick" = "Entfernen"; +"kick" = "Aus Unterhaltung entfernen"; "ban" = "Sperren"; "unban" = "Entsperren"; "message_unsaved_changes" = "Es gibt nicht gespeicherte Änderungen. Verlassen wird diese verwerfen."; @@ -2046,7 +2046,7 @@ // room details dialog screen // contacts list screen -"invitation_message" = "Ich würde gerne über Matrix mit dir chatten. Du kannst dich auf https://matrix.org darüber informieren."; +"invitation_message" = "Ich würde gerne über Matrix mit dir schreiben. Du kannst dich auf https://matrix.org darüber informieren."; // Settings screen "settings_title_config" = "Konfiguration"; "settings_title_notifications" = "Benachrichtigungen"; @@ -2054,7 +2054,7 @@ "notification_settings_disable_all" = "Alle Benachrichtigungen ausschalten"; "notification_settings_enable_notifications" = "Benachrichtigungen einschalten"; "notification_settings_enable_notifications_warning" = "Alle Benachrichtigungen sind momentan für alle Geräte ausgeschaltet."; -"notification_settings_global_info" = "Benachrichtigungseinstellungen werden in deinem Benutzerkonto gespeichert und zwischen allen Clients die das unterstützen geteilt (inklusive Desktop Benachrichtigungen). \n\nRegeln werden der Reihe nach angewandt; die erste Regel, die zutrifft, bestimmt das Resultat für die Nachricht.\nPro-Wort-Benachrichtigungen sind wichtiger als Pro-Raum-Benachrichtigungen, die wichtiger sind wie Pro-Absender-Benachrichtigungen.\nBei mehrfachen Regeln des gleichen Typs wird die erste in der Liste die zutrifft angewendet."; +"notification_settings_global_info" = "Benachrichtigungseinstellungen werden in deinem Benutzerkonto gespeichert und zwischen allen Anwendungen, die das unterstützen geteilt (inklusive Desktop-Benachrichtigungen). \n\nRegeln werden der Reihe nach angewandt; die erste Regel, die zutrifft, bestimmt das Resultat für die Nachricht.\nPro-Wort-Benachrichtigungen sind wichtiger als Pro-Raum-Benachrichtigungen, die wiederum wichtiger als Pro-Absender-Benachrichtigungen sind.\nBei mehrfachen Regeln des gleichen Typs wird, die erste in der Liste, die zutrifft, angewandt."; "notification_settings_per_word_notifications" = "Pro-Wort-Benachrichtigungen"; "notification_settings_per_word_info" = "Suchwörter ignorieren Groß-/Kleinschreibung und können ein *-Platzhalter enthalten. Beispiele:\nfoo findet den String foo umgeben durch Trennzeichen (Satzzeichen, Leerzeichen, Zeilenanfang/ende).\nfoo* findet Worte die mit foo beginnen.\n*foo* findet jedes Wort das foo an beliebiger Stelle enthält."; "notification_settings_always_notify" = "Immer benachrichtigen"; @@ -2129,7 +2129,7 @@ "room_displayname_all_other_members_left" = "%@ (Verlassen)"; "notice_error_unformattable_event" = "** Nachricht kann nicht dargestellt werden. Bitte erstelle einen Bug-Report"; "home_syncing" = "Synchronisiere"; -"settings_labs_use_only_latest_user_avatar_and_name" = "Immer aktuelle Profilbilder und Nicknamen anzeigen"; +"settings_labs_use_only_latest_user_avatar_and_name" = "Aktuelle Profilbilder und Anzeigenamen im Verlauf anzeigen"; "room_participants_leave_success" = "Raum verlassen"; "room_participants_leave_processing" = "Verlassen"; "onboarding_display_name_title" = "Wähle einen Anzeigenamen"; @@ -2220,7 +2220,7 @@ "authentication_forgot_password_waiting_button" = "E-Mail erneut senden"; "authentication_verify_email_input_title" = "Gib deine E-Mail-Adresse ein"; /* The placeholder will show the full Matrix ID that has been entered. */ -"authentication_registration_username_footer_available" = "Andere Nutzer können dich als %@ finden"; +"authentication_registration_username_footer_available" = "Andere können dich als %s finden"; "authentication_registration_username" = "Nutzername"; "onboarding_celebration_message" = "Dein kannst dein Profil jederzeit in den Einstellungen ändern"; "authentication_registration_password_footer" = "8 Zeichen oder mehr"; @@ -2351,7 +2351,7 @@ // Mark: - Space Creation -"spaces_creation_hint" = "Spaces sind eine neue Art, Räume und Personen zu gruppieren."; +"spaces_creation_hint" = "Spaces sind eine neue Möglichkeit, Räume und Personen zu gruppieren."; "space_settings_current_address_message" = "Dein Space ist sichtbar unter\n%@"; "space_settings_update_failed_message" = "Aktualisieren der Space-Einstellungen fehlgeschlagen. Erneut versuchen?"; "space_settings_access_section" = "Wer hat Zugriff auf diesen Space?"; @@ -2398,7 +2398,7 @@ "room_access_space_chooser_other_spaces_section" = "Andere Spaces oder Räume"; "room_access_settings_screen_setting_room_access" = "Lege Raumzugriff fest"; "room_access_settings_screen_upgrade_alert_upgrading" = "Raum upgraden"; -"room_access_settings_screen_upgrade_alert_upgrade_button" = "Upgrade"; +"room_access_settings_screen_upgrade_alert_upgrade_button" = "Aktualisieren"; "room_access_settings_screen_upgrade_alert_auto_invite_switch" = "Mitglieder automatisch zu neuem Raum einladen"; "room_access_settings_screen_upgrade_alert_message" = "Jeder in %@ kann diesen Raum finden und ihm beitreten - jeden manuell einzuladen ist nicht nötig. Du kannst diese Einstellung jederzeit ändern."; "room_access_settings_screen_upgrade_alert_title" = "Raum upgraden"; @@ -2448,16 +2448,16 @@ "room_invites_empty_view_title" = "Nichts neues."; "all_chats_edit_layout_add_filters_title" = "Filtere deine Nachrichten"; "all_chats_edit_layout_unreads" = "Ungelesene"; -"all_chats_section_title" = "Chats"; +"all_chats_section_title" = "Unterhaltungen"; // Mark: - All Chats -"all_chats_title" = "Alle Chats"; +"all_chats_title" = "Alle Unterhaltungen"; "all_chats_empty_view_title" = "%@\nsieht ein bisschen leer aus."; "all_chats_all_filter" = "Alle"; "all_chats_edit_layout_show_filters" = "Filter anzeigen"; "all_chats_edit_menu_leave_space" = "%@ verlassen"; -"all_chats_onboarding_page_title3" = "Feedback geben"; +"all_chats_onboarding_page_title3" = "Rückmeldung geben"; "room_invites_empty_view_information" = "Hier erscheinen deine Einladungen."; "all_chats_onboarding_try_it" = "Probiere es aus"; "threads_discourage_information_1" = "Dein Heimserver unterstützt aktuell keine Threads, weshalb diese Funktion unzuverlässig sein könnte. Manche Thread-Nachrichten könnten nicht zuverlässig verfügbar sein. "; @@ -2465,29 +2465,59 @@ "spaces_create_subspace_title" = "Sub-Space erstellen"; "room_access_settings_screen_upgrade_alert_note" = "Bitte beachte, dass das Upgrade eine neue Version dieses Raums erstellt. Alle aktuellen Nachrichten bleiben in diesem archivierten Raum."; "invite_to" = "Zu %@ einladen"; -"all_chats_empty_unreads_placeholder_message" = "Hier werden ungelesene Nachrichten angezeigt, wenn welche vorhanden sind."; +"all_chats_empty_unreads_placeholder_message" = "Hier werden deine ungelesenen Nachrichten erscheinen, wenn du welche hast."; "all_chats_edit_layout_show_recents" = "Historie anzeigen"; -"all_chats_empty_list_placeholder_title" = "Keine ungelesenen Nachrichten vorhanden."; +"all_chats_empty_list_placeholder_title" = "Du bist auf dem neuesten Stand."; "spaces_explore_rooms_format" = "%@ erkunden"; "room_access_settings_screen_upgrade_alert_message_no_param" = "Jeder in einem übergeordneten Space kann diesen Raum finden und ihm beitreten - jeden manuell einzuladen ist nicht nötig. Du kannst diese Einstellung jederzeit ändern."; "room_access_settings_screen_public_message" = "Sichtbar und zugänglich für jeden."; "room_access_settings_screen_restricted_message" = "Sichtbar und betretbar für jeden Nutzer in einem Space.\nDu wählst, für welche Spaces dies gilt."; "room_access_settings_screen_private_message" = "Nur sichtbar und betretbar für eingeladene Personen."; "location_sharing_allow_background_location_message" = "Wenn du deinen Live-Standort teilen möchtest, benötigt Element den Standortzugriff auch im Hintergrund. Um den Zugriff zu ermöglichen, tippe auf Einstellungen > Standort und wähle ‘Immer‘ aus"; -"space_selector_empty_view_information" = "Spaces sind ein Weg um Räume und Personen zu gruppieren. Erstelle einen Space um loszulegen."; -"all_chats_onboarding_title" = "Was ist neu?"; +"space_selector_empty_view_information" = "Spaces sind eine neue Möglichkeit, Räume und Personen zu gruppieren. Erstelle einen Space, um zu beginnen."; +"all_chats_onboarding_title" = "Was ist neu"; "all_chats_onboarding_page_message3" = "Drücke auf dein Profil um uns Wissen zu lassen, was du denkst."; -"all_chats_onboarding_page_message2" = "Greife auf deine Spaces (unten-links) schneller und einfacher denn je zu."; +"all_chats_onboarding_page_message2" = "Greife auf deine Spaces (unten links) schneller und einfacher denn je zu."; "all_chats_onboarding_page_title2" = "Auf Spaces zugreifen"; -"all_chats_onboarding_page_message1" = "Um Element zu vereinfachen, sind Tabs nun optional. Verwalte diese mit dem Menü oben-rechts."; -"all_chats_empty_view_information" = "Die alles-in-einem sichere Chat-App für Teams, Freunde und Organisationen. Erstelle einen Chat oder trete einem existierenden Raum bei, um zu starten."; -"all_chats_empty_space_information" = "Spaces sind ein neuer Weg um Räume und Personen zu gruppieren. Füge einen existierenden Raum hinzu oder erstelle einen neuen, indem du den Knopf unten-rechts verwendest."; +"all_chats_onboarding_page_message1" = "Um dein Element zu vereinfachen, sind Tabs nun optional. Verwalte sie mit dem Menü oben rechts."; +"all_chats_empty_view_information" = "Die Komplettlösung für sichere Kommunikation unter Freunden, in Gruppen oder in Organisationen. Erstelle eine Unterhaltung oder trete einem bestehenden Raum bei, um loszulegen."; +"all_chats_empty_space_information" = "Spaces sind eine neue Möglichkeit, Räume und Personen zu gruppieren. Füge einen bestehenden Raum hinzu oder erstelle einen neuen mit der Schaltfläche unten rechts."; "all_chats_edit_layout_sorting_options_title" = "Sortiere deine Nachrichten nach"; "space_detail_nav_title" = "Space-Details"; -"all_chats_onboarding_page_title1" = "Willkommen zum neuen Layout!"; +"all_chats_onboarding_page_title1" = "Willkommen in einer neuen Übersicht!"; "all_chats_edit_menu_space_settings" = "Space-Einstellungen"; -"all_chats_user_menu_settings" = "Benutzer-Einstellungen"; +"all_chats_user_menu_settings" = "Nutzereinstellungen"; "room_recents_recently_viewed_section" = "Kürzlich angesehen"; "all_chats_nothing_found_placeholder_message" = "Versuche, deine Suche anzupassen."; "all_chats_edit_layout_recents" = "Historie"; -"all_chats_edit_layout" = "Layout-Einstellungen"; +"all_chats_edit_layout" = "Layouteinstellungen"; +"spaces_creation_new_rooms_message" = "Wir werden für jedes Thema einen Raum erstellen."; +"create_room_section_footer_type_public" = "Sichtbar und betretbar für alle eingeladenen Personen, nicht nur jene, die sich im Space befinden."; + +// First item is client name and second item is session display name +"user_session_name" = "%@: %@"; +"existing" = "Bestehend"; +"device_name_web" = "%@ Web"; +"spaces_subspace_creation_visibility_title" = "Welche Art von Subspace möchtest du erstellen?"; +"all_chats_edit_layout_pin_spaces_title" = "Spaces anpinnen"; +"all_chats_edit_layout_add_filters_message" = "Filtere deine Nachrichten automatisch in Kategorien deiner Wahl"; +"all_chats_edit_layout_add_section_message" = "Abschnitt an Startseite für schnellen Zugriff anpinnen"; +"all_chats_edit_layout_add_section_title" = "Abschnitt zur Startseite hinzufügen"; +"device_name_desktop" = "%@ Desktop"; +"user_sessions_overview_current_session_section_title" = "AKTUELLE SITZUNG"; +"user_sessions_overview_other_sessions_section_title" = "ANDERE SITZUNGEN"; +"device_name_unknown" = "Unbekannte Anwendung"; +"device_name_mobile" = "%@ Mobil"; +"user_session_item_details" = "%@ · Neueste Aktivität %@"; +"user_session_unverified_additional_info" = "Verifiziere deine aktuelle Sitzung für besonders sichere Kommunikation."; +"user_session_verified_additional_info" = "Deine aktuelle Sitzung ist für sichere Kommunikation bereit."; +"user_session_learn_more" = "Mehr erfahren"; +"user_session_view_details" = "Details anzeigen"; +"user_session_verify_action" = "Sitzung verifizieren"; +"user_session_unverified_short" = "Nicht verifiziert"; +"user_session_verified_short" = "Verifiziert"; +"user_session_unverified" = "Nicht verifizierte Sitzungen"; +"user_session_verified" = "Verifizierte Sitzungen"; +"user_sessions_overview_other_sessions_section_info" = "Für bestmögliche Sicherheit verifiziere deine Sitzungen und melde dich von allen ab, die du nicht erkennst oder nutzt."; +"settings_labs_enable_new_app_layout" = "Neues App-Layout"; +"room_first_message_placeholder" = "Schreibe deine erste Nachricht …"; From 504b3313668d1db33f6c3f2b92992162692308af Mon Sep 17 00:00:00 2001 From: Kaede Date: Sun, 25 Sep 2022 23:08:05 +0000 Subject: [PATCH 071/104] Translated using Weblate (Japanese) Currently translated at 69.6% (1530 of 2198 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ja/ --- Riot/Assets/ja.lproj/Vector.strings | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/Riot/Assets/ja.lproj/Vector.strings b/Riot/Assets/ja.lproj/Vector.strings index 8b337a5ab..df4ef10d1 100644 --- a/Riot/Assets/ja.lproj/Vector.strings +++ b/Riot/Assets/ja.lproj/Vector.strings @@ -541,7 +541,7 @@ "deactivate_account_forget_messages_information_part3" = ":これは将来のユーザーに会話の不完全なビューが表示される)"; "deactivate_account_validate_action" = "無効なアカウント"; "deactivate_account_password_alert_title" = "無効なアカウント"; -"deactivate_account_password_alert_message" = "続行するには、パスワードを入力してください"; +"deactivate_account_password_alert_message" = "続行するには、Matrix アカウントのパスワードを入力してください"; // Re-request confirmation dialog "rerequest_keys_alert_title" = "要求が送信されました"; "rerequest_keys_alert_message" = "鍵をこのセッションに送信できるように、メッセージを復号化できる他の端末で%@を起動してください。"; @@ -707,7 +707,7 @@ "settings_key_backup_info_version" = "鍵のバックアップのバージョン:%@"; "settings_key_backup_info_none" = "あなたの鍵は、このセッションからバックアップされていません。"; "settings_key_backup_info_checking" = "確認しています…"; -"settings_add_3pid_password_message" = "続行するには、パスワードを入力してください"; +"settings_add_3pid_password_message" = "続行するには、Matrix アカウントのパスワードを入力してください"; "settings_add_3pid_invalid_password_message" = "無効な認証情報"; "settings_add_3pid_password_title_email" = "メールアドレスを追加"; "settings_integrations_allow_description" = "インテグレーションマネージャー(%@)を使用して、ボット、ブリッジ、ウィジェット、ステッカーパックを管理します。\n\n設定データを受け取り、お客様に代わってウィジェットの変更、ルーム招待の送信、権限の設定を行うことができます。"; @@ -1756,8 +1756,8 @@ "room_notifs_settings_notify_me_for" = "以下の場合に通知"; "room_details_access_row_title" = "アクセス"; "room_details_notifs" = "通知"; -"location_sharing_invalid_power_level_title" = "現在地の共有に必要な権限がありません"; -"settings_labs_enable_live_location_sharing" = "現在地の共有 - 現在地を共有します (開発中の機能、位置情報が一時的にルームの履歴に残ります)"; +"location_sharing_invalid_power_level_title" = "ライブ現在地共有に必要な権限がありません"; +"settings_labs_enable_live_location_sharing" = "ライブ現在地共有 - 現在地を共有します (開発中の機能、位置情報が一時的にルームの履歴に残ります)"; "event_formatter_message_deleted" = "削除済みのメッセージ"; "home_context_menu_unfavourite" = "お気に入りから削除"; "home_context_menu_favourite" = "お気に入り"; @@ -1776,3 +1776,20 @@ // Mark: - All Chats "all_chats_title" = "全てのチャット"; +"location_sharing_live_loading" = "ライブ現在地共有を読み込み中..."; +"location_sharing_live_list_item_stop_sharing_action" = "停止"; +"location_sharing_live_list_item_current_user_display_name" = "あなた"; +"live_location_sharing_banner_stop" = "停止"; +"live_location_sharing_ended" = "ライブ現在地共有が終了"; +"live_location_sharing_banner_title" = "ライブ現在地共有が有効"; +"location_sharing_invalid_authorization_settings" = "設定"; + +// MARK: Live location sharing + +"location_sharing_live_share_title" = "ライブ現在地共有"; +"service_terms_modal_decline_button" = "拒否する"; +"service_terms_modal_accept_button" = "同意する"; +"service_terms_modal_description_identity_server" = "この操作により、端末の連絡先にあなたの電話番号や電子メールを保存している人があなたを検索できるようになります。"; + +// Service terms +"service_terms_modal_title_message" = "続行するには、以下の利用規約に同意してください"; From dbd15400f31a5fccf5350c24cab8045b25a90949 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Sat, 24 Sep 2022 15:17:15 +0000 Subject: [PATCH 072/104] Translated using Weblate (Hungarian) Currently translated at 100.0% (2198 of 2198 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index ee4805a2d..87b74c5bb 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -2485,3 +2485,25 @@ // User sessions management "user_sessions_settings" = "Munkamenetek kezelése"; "invite_to" = "Meghívó ide: %@"; +"device_name_unknown" = "Ismeretlen kliens"; +"device_name_mobile" = "%@ Mobil"; +"device_name_desktop" = "%@ Alkalmazás"; +"device_name_web" = "%@ Web"; +"user_session_item_details" = "%@ · Utolsó aktivitás %@"; + +// First item is client name and second item is session display name +"user_session_name" = "%@: %@"; +"user_session_unverified_additional_info" = "Az aktuális munkamenet készen áll a biztonságos üzenetküldésre."; +"user_session_verified_additional_info" = "Az aktuális munkamenet készen áll a biztonságos üzenetküldésre."; +"user_session_learn_more" = "Tudj meg többet"; +"user_session_view_details" = "Részletek megtekintése"; +"user_session_verify_action" = "Munkamenet hitelesítése"; +"user_session_unverified_short" = "Ellenőrizetlen"; +"user_session_verified_short" = "Hitelesített"; +"user_session_unverified" = "Ellenőrizetlen munkamenet"; +"user_session_verified" = "Ellenőrzött munkamenet"; +"user_sessions_overview_current_session_section_title" = "JELENLEGI MUNKAMENET"; +"user_sessions_overview_other_sessions_section_info" = "A legjobb biztonság érdekében ellenőrizd a munkameneteket, és jelentkezz ki minden olyan munkamenetből, melyet már nem ismersz fel vagy nem használsz."; +"user_sessions_overview_other_sessions_section_title" = "TOVÁBBI MUNKAMENETEK"; +"settings_labs_enable_new_app_layout" = "Új alkalmazás kinézet"; +"room_first_message_placeholder" = "Küld el az első üzenetedet…"; From 52cb08d6df33b18efbcfe65b8ed9472d20bb66a4 Mon Sep 17 00:00:00 2001 From: random Date: Mon, 26 Sep 2022 13:03:20 +0000 Subject: [PATCH 073/104] Translated using Weblate (Italian) Currently translated at 100.0% (2198 of 2198 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ --- Riot/Assets/it.lproj/Vector.strings | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index 55b76e8c7..ec31cebb3 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -2465,3 +2465,25 @@ // User sessions management "user_sessions_settings" = "Gestisci sessioni"; "invite_to" = "Invita in %@"; +"device_name_unknown" = "Client sconosciuto"; +"device_name_mobile" = "%@ Mobile"; +"device_name_web" = "%@ Web"; +"device_name_desktop" = "%@ Desktop"; +"user_session_item_details" = "%@ · Ultima attività %@"; + +// First item is client name and second item is session display name +"user_session_name" = "%@: %@"; +"user_session_unverified_additional_info" = "Verifica la tua sessione attuale per messaggi più sicuri."; +"user_session_verified_additional_info" = "La tua sessione attuale è pronta per i messaggi sicuri."; +"user_session_learn_more" = "Maggiori informazioni"; +"user_session_view_details" = "Vedi dettagli"; +"user_session_verify_action" = "Verifica la sessione"; +"user_session_unverified_short" = "Non verificata"; +"user_session_verified_short" = "Verificata"; +"user_session_unverified" = "Sessione non verificata"; +"user_session_verified" = "Sessione verificata"; +"user_sessions_overview_current_session_section_title" = "SESSIONE ATTUALE"; +"user_sessions_overview_other_sessions_section_info" = "Per una maggiore sicurezza, verifica le tue sessioni e disconnetti quelle che non riconosci o che non usi più."; +"user_sessions_overview_other_sessions_section_title" = "ALTRE SESSIONI"; +"settings_labs_enable_new_app_layout" = "Nuova disposizione dell'applicazione"; +"room_first_message_placeholder" = "Invia il tuo primo messaggio…"; From 0e89a9d7b1718f045a69383aa0f17272454e3a9c Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Fri, 23 Sep 2022 20:01:05 +0000 Subject: [PATCH 074/104] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2198 of 2198 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ --- Riot/Assets/pt_BR.lproj/Vector.strings | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Riot/Assets/pt_BR.lproj/Vector.strings b/Riot/Assets/pt_BR.lproj/Vector.strings index ca607998b..52be5eaa8 100644 --- a/Riot/Assets/pt_BR.lproj/Vector.strings +++ b/Riot/Assets/pt_BR.lproj/Vector.strings @@ -2466,3 +2466,25 @@ // User sessions management "user_sessions_settings" = "Gerenciar sessões"; "invite_to" = "Convidar para %@"; +"device_name_unknown" = "Cliente desconhecido"; +"device_name_mobile" = "%@ Mobile"; +"device_name_web" = "%@ Web"; +"device_name_desktop" = "%@ Desktop"; +"user_session_item_details" = "%@ · Última atividade %@"; + +// First item is client name and second item is session display name +"user_session_name" = "%@: %@"; +"user_session_unverified_additional_info" = "Verifique sua sessão atual para mensageria segura melhorada."; +"user_session_verified_additional_info" = "Sua sessão atual está pronta para mensageria segura."; +"user_session_learn_more" = "Saber mais"; +"user_session_view_details" = "Visualizar detalhes"; +"user_session_verify_action" = "Verificar sessão"; +"user_session_unverified_short" = "Não-verificada"; +"user_session_verified_short" = "Verificada"; +"user_session_unverified" = "Sessão não-verificada"; +"user_session_verified" = "Sessão verificada"; +"user_sessions_overview_current_session_section_title" = "SESSÃO ATUAL"; +"user_sessions_overview_other_sessions_section_info" = "Para melhor segurança, verifique suas sessões e faça signout de qualquer sessão que você não reconhece ou usa mais."; +"user_sessions_overview_other_sessions_section_title" = "OUTRAS SESSÕES"; +"settings_labs_enable_new_app_layout" = "Novo Layout de Aplicativo"; +"room_first_message_placeholder" = "Envie sua primeira mensagem…"; From f3a1f2d6886b0934d6e700f3c976adba17eb37a0 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Fri, 23 Sep 2022 19:12:57 +0000 Subject: [PATCH 075/104] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2198 of 2198 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ --- Riot/Assets/uk.lproj/Vector.strings | 34 ++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/Riot/Assets/uk.lproj/Vector.strings b/Riot/Assets/uk.lproj/Vector.strings index 783f461a6..4d561fe3d 100644 --- a/Riot/Assets/uk.lproj/Vector.strings +++ b/Riot/Assets/uk.lproj/Vector.strings @@ -227,7 +227,7 @@ "room_directory_no_public_room" = "Загальнодоступних кімнат немає"; "settings_security" = "БЕЗПЕКА"; "settings_pin_rooms_with_missed_notif" = "Приколоти кімнати із пропущеними сповіщеннями"; -"settings_global_settings_info" = "Глобальні налаштування сповіщень доступні у вашому %@ веб-клієнті"; +"settings_global_settings_info" = "Глобальні налаштування сповіщень доступні у вашому %@ вебклієнті"; "settings_enable_push_notif" = "Сповіщення на цьому пристрої"; "settings_fail_to_update_profile" = "Не вдалося оновити профіль"; "settings_night_mode" = "Нічний режим"; @@ -684,7 +684,7 @@ "space_beta_announce_badge" = "БЕТА"; "space_beta_announce_title" = "Простори скоро з'являться"; "space_beta_announce_subtitle" = "Нова версія спільнот"; -"space_beta_announce_information" = "Простори — це новий спосіб об'єднати кімнати та людей. Їх ще не додано на iOS, але ви вже можете користуватися ними у вебпереглядачі та на комп'ютері."; +"space_beta_announce_information" = "Простори — це новий спосіб об'єднати кімнати та людей. Їх ще не додано на iOS, але ви вже можете користуватися ними у браузері та на комп'ютері."; // No VoIP support "no_voip_title" = "Вхідний виклик"; @@ -1066,7 +1066,7 @@ "room_participants_start_new_chat_error_using_user_email_without_identity_server" = "Поки жоден сервер ідентифікації не налаштований, ви не можете почати бесіду з кимось за адресою е-пошти."; "find_your_contacts_message" = "Дозвольте %@ показувати ваші контакти, щоб ви могли швидко почати бесіду з тими, кого знаєте найкраще."; "find_your_contacts_title" = "Почніть із переліку своїх контактів"; -"store_full_description" = "Element — застосунок для листування й співпраці нового покоління:\n\n1. Надає вам контроль над збереженням вашої приватності\n2. Дає змогу спілкуватися з будь-ким у мережі Matrix і навіть за її межами, інтегруючись із такими застосунками, як Slack\n3. Оберігає вас від реклами, збору даних, бекдорів і прив'язаності до провайдера\n4. Захищає вас наскрізним шифруванням і звіркою інших перехресним підписуванням\n\nElement суттєво відрізняється від інших застосунків для листування й співпраці тим, що децентралізований і має відкритий код.\n\nElement дає змогу самостійно встановити сервер або обрати з-поміж загальнодоступних, щоб ви зберігали приватність своїх даних і розмов, власність і контроль над ними. Він надає вам доступ до відкритої мережі; тож ви можете спілкуватися з користувачами інших застосунків, не лише Element. А ще він добре захищений.\n\nElement здатен на це все завдяки своїй основі Matrix — стандарту відкритого, децентралізованого спілкування.\n\nElement надає вам контроль, даючи змогу обрати, в кого зберігаються ваші розмови. У застосунку Element ви можете обрати між такими шляхами:\n\n1. Зареєструвати безплатний обліковий запис на загальнодоступному сервері matrix.org\n2. Самостійно розмістити свій обліковий запис, встановивши сервер на власному обладнанні\n3. Отримати обліковий запис на виділеному сервері, просто передплативши хостинг-платформу Element Matrix Services\n\nЧому Element?\n\nВОЛОДІЙТЕ СВОЇМИ ДАНИМИ: Ви обираєте, де зберігати свої дані й повідомлення. Ви володієте й керуєте ними, не якась МЕГАКОРПОРАЦІЯ, що аналізує ваші дані й передає їх стороннім особам.\n\nВІДКРИТЕ ЛИСТУВАННЯ Й СПІВПРАЦЯ: Можете розмовляти з будь-ким іншим у мережі Matrix незалежно від того, використовують вони Element, інший застосунок Matrix чи навіть сторонню систему листування на зразок Slack, IRC чи XMPP.\n\nНАДБЕЗПЕКА: Справжнє наскрізне шифрування (лише учасники розмови можуть розшифрувати повідомлення) й звірка пристроїв учасників розмови перехресним підписуванням.\n\nДОСКОНАЛЕ СПІЛКУВАННЯ: Листуйтеся, робіть голосові й відеовиклики, діліться файлами, транслюйте екран, підʼєднуйте різноманітні інтеграції, ботів і віджети. Розбудовуйте кімнати, спільноти, будьте на звʼязку й досягайте цілей.\n\nСКРІЗЬ, ДЕ ВИ: Будьте на зв'язку, де б ви не були, завдяки повній синхронізації історії повідомлень між усіма вашим пристроями та онлайн-клієнтом https://app.element.io."; +"store_full_description" = "Element — застосунок для листування й співпраці нового покоління:\n\n1. Надає вам контроль над збереженням вашої приватності\n2. Дає змогу спілкуватися з будь-ким у мережі Matrix і навіть за її межами, інтегруючись із такими застосунками, як Slack\n3. Оберігає вас від реклами, збору даних, бекдорів і прив'язаності до провайдера\n4. Захищає вас наскрізним шифруванням і звіркою інших перехресним підписуванням\n\nElement суттєво відрізняється від інших застосунків для листування й співпраці тим, що децентралізований і має відкритий код.\n\nElement дає змогу самостійно встановити сервер або обрати з-поміж загальнодоступних, щоб ви зберігали приватність своїх даних і розмов, власність і контроль над ними. Він надає вам доступ до відкритої мережі; тож ви можете спілкуватися з користувачами інших застосунків, не лише Element. А ще він добре захищений.\n\nElement здатен на це все завдяки своїй основі Matrix — стандарту відкритого, децентралізованого спілкування.\n\nElement надає вам контроль, даючи змогу обрати, в кого зберігаються ваші розмови. У застосунку Element ви можете обрати між такими шляхами:\n\n1. Зареєструвати безплатний обліковий запис на загальнодоступному сервері matrix.org\n2. Самостійно розмістити свій обліковий запис, встановивши сервер на власному обладнанні\n3. Отримати обліковий запис на виділеному сервері, просто передплативши хостинг-платформу Element Matrix Services\n\nЧому Element?\n\nВОЛОДІЙТЕ СВОЇМИ ДАНИМИ: Ви обираєте, де зберігати свої дані й повідомлення. Ви володієте й керуєте ними, не якась МЕГАКОРПОРАЦІЯ, що аналізує ваші дані й передає їх стороннім особам.\n\nВІДКРИТЕ ЛИСТУВАННЯ Й СПІВПРАЦЯ: Можете розмовляти з будь-ким іншим у мережі Matrix незалежно від того, використовують вони Element, інший застосунок Matrix чи навіть сторонню систему листування на зразок Slack, IRC чи XMPP.\n\nНАДБЕЗПЕКА: Справжнє наскрізне шифрування (лише учасники розмови можуть розшифрувати повідомлення) й звірка пристроїв учасників розмови перехресним підписуванням.\n\nДОСКОНАЛЕ СПІЛКУВАННЯ: Листуйтеся, робіть голосові й відеовиклики, діліться файлами, транслюйте екран, підʼєднуйте різноманітні інтеграції, ботів і віджети. Розбудовуйте кімнати, спільноти, будьте на звʼязку й досягайте цілей.\n\nСКРІЗЬ, ДЕ ВИ: Будьте на зв'язку, де б ви не були, завдяки повній синхронізації історії повідомлень між усіма вашими пристроями та вебклієнтом https://app.element.io."; "security_settings_crosssigning_info_not_bootstrapped" = "Перехресне підписування ще не налаштовано."; "security_settings_crosssigning" = "ПЕРЕХРЕСНЕ ПІДПИСУВАННЯ"; "security_settings_backup" = "РЕЗЕРВНЕ КОПІЮВАННЯ ПОВІДОМЛЕНЬ"; @@ -1386,7 +1386,7 @@ "room_intro_cell_information_room_without_topic_sentence2_part2" = " щоб люди знали, про що ця кімната."; "room_intro_cell_information_dm_sentence2" = "У цій розмові лише ви двоє, більше ніхто не може приєднатися."; "room_intro_cell_information_multiple_dm_sentence2" = "У цій розмові лише ви, поки хтось із вас не запросить іще когось приєднатися."; -"space_feature_unavailable_subtitle" = "Простори ще не готові на iOS, але доступні у вебпереглядачі та на комп'ютері"; +"space_feature_unavailable_subtitle" = "Простори ще не готові на iOS, але доступні у браузері та на комп'ютері"; "space_feature_unavailable_information" = "Простори — новий спосіб групувати кімнати й людей.\n\nСкоро ми їх допрацюємо. Якщо приєднаєтеся до простору на іншій платформі, всі його кімнати стануть доступні й тут."; // Success from secure backup @@ -1408,7 +1408,7 @@ "spaces_suggested_room" = "Пропоновано"; "room_details_access_section_anyone_for_dm" = "Будь-хто з посиланням, зокрема гості"; "spaces_no_room_found_detail" = "Деяких результатів може бути не видно, бо вони закриті й потребують запрошення."; -"spaces_no_member_found_detail" = "Шукаєте когось, хто ще не в %@? Запросіть їх вебпереглядачем або комп'ютером."; +"spaces_no_member_found_detail" = "Шукаєте когось, хто ще не в %@? Запросіть їх через браузер або комп'ютер."; "spaces_invites_coming_soon_title" = "Запрошення ще в розробці"; "spaces_coming_soon_detail" = "Ця можливість ще не втілена, але скоро буде. Поки що можете зробити це в %@ на комп'ютері."; "space_participants_action_remove" = "Вилучити з цього простору"; @@ -1560,7 +1560,7 @@ "widget_integration_manager_disabled" = "Необхідно увімкнути менеджер інтеграцій у налаштуваннях"; "widget_menu_remove" = "Вилучити для всіх"; "widget_menu_revoke_permission" = "Відкликати мій доступ"; -"widget_menu_open_outside" = "Відкрити у переглядачі"; +"widget_menu_open_outside" = "Відкрити у браузері"; "widget_menu_refresh" = "Оновити"; "widget_sticker_picker_no_stickerpacks_alert_add_now" = "Додати зараз?"; "widget_sticker_picker_no_stickerpacks_alert" = "На разі жоден пакунок наліпок не увімкнено."; @@ -2690,3 +2690,25 @@ // User sessions management "user_sessions_settings" = "Керувати сеансами"; "invite_to" = "Запросити до %@"; +"device_name_unknown" = "Невідомий клієнт"; +"device_name_mobile" = "%@ Мобільний"; +"device_name_web" = "%@ Браузер"; +"device_name_desktop" = "%@ Комп'ютер"; +"user_session_item_details" = "%@ · Остання активність %@"; + +// First item is client name and second item is session display name +"user_session_name" = "%@: %@"; +"user_session_unverified_additional_info" = "Звірте свій поточний сеанс для посилення безпеки обміну повідомленнями."; +"user_session_verified_additional_info" = "Ваш поточний сеанс готовий до безпечного обміну повідомленнями."; +"user_session_learn_more" = "Докладніше"; +"user_session_view_details" = "Переглянути подробиці"; +"user_session_verify_action" = "Звірити сеанс"; +"user_session_unverified_short" = "Не звірений"; +"user_session_verified_short" = "Звірений"; +"user_session_unverified" = "Не звірений сеанс"; +"user_session_verified" = "Звірений сеанс"; +"user_sessions_overview_current_session_section_title" = "ПОТОЧНИЙ СЕАНС"; +"user_sessions_overview_other_sessions_section_info" = "Звірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте для кращої безпеки."; +"user_sessions_overview_other_sessions_section_title" = "ІНШІ СЕАНСИ"; +"settings_labs_enable_new_app_layout" = "Новий вигляд застосунку"; +"room_first_message_placeholder" = "Надішліть своє перше повідомлення…"; From f01b580f79cb9eff1db95e4d513da4a0794f553d Mon Sep 17 00:00:00 2001 From: Linerly Date: Fri, 23 Sep 2022 12:04:17 +0000 Subject: [PATCH 076/104] Translated using Weblate (Indonesian) Currently translated at 100.0% (2198 of 2198 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ --- Riot/Assets/id.lproj/Vector.strings | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Riot/Assets/id.lproj/Vector.strings b/Riot/Assets/id.lproj/Vector.strings index 1bf9d6fa1..d698f6115 100644 --- a/Riot/Assets/id.lproj/Vector.strings +++ b/Riot/Assets/id.lproj/Vector.strings @@ -2692,3 +2692,25 @@ // User sessions management "user_sessions_settings" = "Kelola sesi"; "invite_to" = "Undang ke %@"; +"device_name_unknown" = "Klien tidak diketahui"; +"device_name_mobile" = "%@ Ponsel"; +"device_name_web" = "%@ Web"; +"device_name_desktop" = "%@ Desktop"; +"user_session_item_details" = "%@ · Aktivitas terakhir %@"; + +// First item is client name and second item is session display name +"user_session_name" = "%@: %@"; +"user_session_unverified_additional_info" = "Verifikasi sesi Anda saat ini untuk perpesanan aman yang baik."; +"user_session_verified_additional_info" = "Sesi Anda saat ini siap untuk perpesanan aman."; +"user_session_learn_more" = "Pelajari lebih lanjut"; +"user_session_view_details" = "Tampilkan detail"; +"user_session_verify_action" = "Verifikasi sesi"; +"user_session_unverified_short" = "Belum diverifikasi"; +"user_session_verified_short" = "Terverifikasi"; +"user_session_unverified" = "Sesi belum diverifikasi"; +"user_session_verified" = "Sesi terverifikasi"; +"user_sessions_overview_current_session_section_title" = "SESI SAAT INI"; +"user_sessions_overview_other_sessions_section_info" = "Untuk keamanan yang terbaik, verifikasi sesi Anda dan keluarkan dari sesi yang Anda tidak kenal atau tidak digunakan lagi."; +"user_sessions_overview_other_sessions_section_title" = "SESI LAINNYA"; +"settings_labs_enable_new_app_layout" = "Tata Letak Aplikasi Baru"; +"room_first_message_placeholder" = "Kirim pesan pertama Anda…"; From 2b463869f562344058341fb3b9b5286f030855b9 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Fri, 23 Sep 2022 13:12:39 +0000 Subject: [PATCH 077/104] Translated using Weblate (Slovak) Currently translated at 100.0% (2198 of 2198 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ --- Riot/Assets/sk.lproj/Vector.strings | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Riot/Assets/sk.lproj/Vector.strings b/Riot/Assets/sk.lproj/Vector.strings index ecfcb9734..5cecf24a5 100644 --- a/Riot/Assets/sk.lproj/Vector.strings +++ b/Riot/Assets/sk.lproj/Vector.strings @@ -2688,3 +2688,25 @@ // User sessions management "user_sessions_settings" = "Spravovať relácie"; "invite_to" = "Pozvať do %@"; +"device_name_web" = "%@ Web"; +"device_name_unknown" = "Neznámy klient"; +"device_name_mobile" = "%@ Mobil"; +"device_name_desktop" = "%@ Stolný počítač"; +"user_session_item_details" = "%@ · Posledná aktivita %@"; + +// First item is client name and second item is session display name +"user_session_name" = "%@: %@"; +"user_session_unverified_additional_info" = "Overte svoju aktuálnu reláciu pre vylepšené bezpečné zasielanie správ."; +"user_session_verified_additional_info" = "Vaša aktuálna relácia je pripravená na bezpečné zasielanie správ."; +"user_session_learn_more" = "Zistiť viac"; +"user_session_view_details" = "Zobraziť podrobnosti"; +"user_session_verify_action" = "Overiť reláciu"; +"user_session_unverified_short" = "Neoverené"; +"user_session_verified_short" = "Overené"; +"user_session_unverified" = "Neoverená relácia"; +"user_session_verified" = "Overená relácia"; +"user_sessions_overview_current_session_section_title" = "AKTUÁLNA RELÁCIA"; +"user_sessions_overview_other_sessions_section_info" = "V záujme čo najlepšieho zabezpečenia overte svoje relácie a odhláste sa z každej relácie, ktorú už nepoznáte alebo nepoužívate."; +"user_sessions_overview_other_sessions_section_title" = "OSTATNÉ RELÁCIE"; +"settings_labs_enable_new_app_layout" = "Nové usporiadanie aplikácie"; +"room_first_message_placeholder" = "Pošlite svoju prvú správu…"; From 4e27b5f0d7e4a3e5a0dab48b018da1437286c7b4 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Wed, 28 Sep 2022 14:39:01 +0300 Subject: [PATCH 078/104] Removed conditional view modifier --- .../Extensions/ConditionalViewModifier.swift | 27 ------------------- .../Common/View/UserSessionCardView.swift | 6 ++--- 2 files changed, 3 insertions(+), 30 deletions(-) delete mode 100644 RiotSwiftUI/Modules/Common/Extensions/ConditionalViewModifier.swift diff --git a/RiotSwiftUI/Modules/Common/Extensions/ConditionalViewModifier.swift b/RiotSwiftUI/Modules/Common/Extensions/ConditionalViewModifier.swift deleted file mode 100644 index 14f5245aa..000000000 --- a/RiotSwiftUI/Modules/Common/Extensions/ConditionalViewModifier.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright 2022 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import SwiftUI - -extension View { - @ViewBuilder func `if`(_ condition: @autoclosure () -> Bool, transform: (Self) -> Content) -> some View { - if condition() { - transform(self) - } else { - self - } - } -} diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift index 460f273b3..a54915e23 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift @@ -122,10 +122,10 @@ struct UserSessionCardView: View { .background(theme.colors.background) .clipShape(backgroundShape) .shapedBorder(color: theme.colors.quinaryContent, borderWidth: 1.0, shape: backgroundShape) - .if(viewData.isCurrentSessionDisplayMode) { view in - view.onTapGesture { + .onTapGesture { + if viewData.isCurrentSessionDisplayMode { onViewDetailsAction?(viewData.sessionId) - } + } } } } From f89b54e73327adc76f79d890b96ad0bb4c0b5d20 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Wed, 28 Sep 2022 12:54:10 +0100 Subject: [PATCH 079/104] Fix verification check --- .../Scanning/KeyVerificationVerifyByScanningViewModel.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift index d0d900290..fe54a2467 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift @@ -172,8 +172,9 @@ final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyBySca // Remove pending QR code transaction, as we are going to use SAS verification self.removePendingQRCodeTransaction() - - if keyVerificationTransaction is MXSASTransaction == false || keyVerificationTransaction.isIncoming { + + // Check due to legacy implementation of key verification which could pass incorrect type of transaction + if keyVerificationTransaction is MXIncomingSASTransaction { MXLog.debug("[KeyVerificationVerifyByScanningViewModel] SAS transaction should be outgoing") self.unregisterTransactionDidStateChangeNotification() self.update(viewState: .error(KeyVerificationVerifyByScanningViewModelError.unknown)) From 89d4bb245267029ce5a988a6f85185b0758132db Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 27 Sep 2022 16:51:13 +0300 Subject: [PATCH 080/104] Implemented PSG-680 - Sessions overview security recommendations - remove uppercased strings from localizables - moved inactivity computation to the service --- .../Contents.json | 12 ++ .../user_sessions_inactive.svg | 11 ++ .../Contents.json | 12 ++ .../user_sessions_unverified.svg | 5 + Riot/Assets/en.lproj/Vector.strings | 20 +++- Riot/Generated/Images.swift | 2 + Riot/Generated/Strings.swift | 36 +++++- .../UserSessions/Common/UserSessionInfo.swift | 42 +------ .../Common/View/UserSessionCardView.swift | 7 +- .../Common/View/UserSessionCardViewData.swift | 6 +- .../MockUserSessionDetailsScreenState.swift | 14 ++- .../UserSessionDetailsViewModelTests.swift | 8 +- .../UserSessionDetailsViewModel.swift | 8 +- .../MockUserSessionOverviewScreenState.swift | 14 ++- .../UserSessionOverviewViewModel.swift | 2 +- .../UserSessionsOverviewService.swift | 22 +++- .../MockUserSessionsOverviewService.swift | 54 +++++++-- .../View/SecurityRecommendationCard.swift | 111 ++++++++++++++++++ .../View/UserSessionListItemViewData.swift | 6 +- .../View/UserSessionsOverview.swift | 104 ++++++++++------ 20 files changed, 369 insertions(+), 127 deletions(-) create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_sessions_inactive.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_sessions_inactive.imageset/user_sessions_inactive.svg create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_sessions_unverified.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_sessions_unverified.imageset/user_sessions_unverified.svg create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/SecurityRecommendationCard.swift diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_sessions_inactive.imageset/Contents.json b/Riot/Assets/Images.xcassets/DeviceManager/user_sessions_inactive.imageset/Contents.json new file mode 100644 index 000000000..7261e4ae4 --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_sessions_inactive.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "user_sessions_inactive.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_sessions_inactive.imageset/user_sessions_inactive.svg b/Riot/Assets/Images.xcassets/DeviceManager/user_sessions_inactive.imageset/user_sessions_inactive.svg new file mode 100644 index 000000000..d7baa4786 --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_sessions_inactive.imageset/user_sessions_inactive.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_sessions_unverified.imageset/Contents.json b/Riot/Assets/Images.xcassets/DeviceManager/user_sessions_unverified.imageset/Contents.json new file mode 100644 index 000000000..2c0d97214 --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_sessions_unverified.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "user_sessions_unverified.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_sessions_unverified.imageset/user_sessions_unverified.svg b/Riot/Assets/Images.xcassets/DeviceManager/user_sessions_unverified.imageset/user_sessions_unverified.svg new file mode 100644 index 000000000..4db2abe67 --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_sessions_unverified.imageset/user_sessions_unverified.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 7165bdcd2..79cf31ba0 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2358,10 +2358,21 @@ To enable access, tap Settings> Location and select Always"; "user_sessions_overview_title" = "Sessions"; -"user_sessions_overview_other_sessions_section_title" = "OTHER SESSIONS"; +"user_sessions_overview_security_recommendations_section_title" = "Security recommendations"; +"user_sessions_overview_security_recommendations_section_info" = "Improve your account security by following these recommendations."; + +"user_sessions_overview_security_recommendations_unverified_title" = "Unverified sessions"; +"user_sessions_overview_security_recommendations_unverified_info" = "Verify or sign out from unverified sessions."; + +"user_sessions_overview_security_recommendations_inactive_title" = "Inactive sessions"; +"user_sessions_overview_security_recommendations_inactive_info" = "Consider signing out from old sessions (90 days or older) you don’t use anymore."; + +"user_sessions_overview_other_sessions_section_title" = "Other sessions"; "user_sessions_overview_other_sessions_section_info" = "For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore."; -"user_sessions_overview_current_session_section_title" = "CURRENT SESSION"; +"user_sessions_overview_current_session_section_title" = "Current session"; + +"user_sessions_view_all_action" = "View all (%d)"; "user_session_verified" = "Verified session"; "user_session_unverified" = "Unverified session"; @@ -2374,6 +2385,7 @@ To enable access, tap Settings> Location and select Always"; "user_session_verified_additional_info" = "Your current session is ready for secure messaging."; "user_session_unverified_additional_info" = "Verify your current session for enhanced secure messaging."; + // First item is client name and second item is session display name "user_session_name" = "%@: %@"; @@ -2385,8 +2397,8 @@ To enable access, tap Settings> Location and select Always"; "device_name_unknown" = "Unknown client"; "user_session_details_title" = "Session details"; -"user_session_details_session_section_header" = "SESSION"; -"user_session_details_device_section_header" = "DEVICE"; +"user_session_details_session_section_header" = "Session"; +"user_session_details_device_section_header" = "Device"; "user_session_details_session_name" = "Session name"; "user_session_details_session_id" = "Session ID"; "user_session_details_session_section_footer" = "Copy any data by tapping on it and holding it down."; diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 6fc13773b..9dc652238 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -107,6 +107,8 @@ internal class Asset: NSObject { internal static let deviceTypeWeb = ImageAsset(name: "device_type_web") internal static let userSessionUnverified = ImageAsset(name: "user_session_unverified") internal static let userSessionVerified = ImageAsset(name: "user_session_verified") + internal static let userSessionsInactive = ImageAsset(name: "user_sessions_inactive") + internal static let userSessionsUnverified = ImageAsset(name: "user_sessions_unverified") internal static let e2eBlocked = ImageAsset(name: "e2e_blocked") internal static let e2eUnencrypted = ImageAsset(name: "e2e_unencrypted") internal static let e2eWarning = ImageAsset(name: "e2e_warning") diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 00249e843..31d85976d 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -8471,7 +8471,7 @@ public class VectorL10n: NSObject { public static var userSessionDetailsDeviceIpAddress: String { return VectorL10n.tr("Vector", "user_session_details_device_ip_address") } - /// DEVICE + /// Device public static var userSessionDetailsDeviceSectionHeader: String { return VectorL10n.tr("Vector", "user_session_details_device_section_header") } @@ -8487,7 +8487,7 @@ public class VectorL10n: NSObject { public static var userSessionDetailsSessionSectionFooter: String { return VectorL10n.tr("Vector", "user_session_details_session_section_footer") } - /// SESSION + /// Session public static var userSessionDetailsSessionSectionHeader: String { return VectorL10n.tr("Vector", "user_session_details_session_section_header") } @@ -8551,7 +8551,7 @@ public class VectorL10n: NSObject { public static var userSessionViewDetails: String { return VectorL10n.tr("Vector", "user_session_view_details") } - /// CURRENT SESSION + /// Current session public static var userSessionsOverviewCurrentSessionSectionTitle: String { return VectorL10n.tr("Vector", "user_sessions_overview_current_session_section_title") } @@ -8559,10 +8559,34 @@ public class VectorL10n: NSObject { public static var userSessionsOverviewOtherSessionsSectionInfo: String { return VectorL10n.tr("Vector", "user_sessions_overview_other_sessions_section_info") } - /// OTHER SESSIONS + /// Other sessions public static var userSessionsOverviewOtherSessionsSectionTitle: String { return VectorL10n.tr("Vector", "user_sessions_overview_other_sessions_section_title") } + /// Consider signing out from old sessions (90 days or older) you don’t use anymore. + public static var userSessionsOverviewSecurityRecommendationsInactiveInfo: String { + return VectorL10n.tr("Vector", "user_sessions_overview_security_recommendations_inactive_info") + } + /// Inactive sessions + public static var userSessionsOverviewSecurityRecommendationsInactiveTitle: String { + return VectorL10n.tr("Vector", "user_sessions_overview_security_recommendations_inactive_title") + } + /// Improve your account security by following these recommendations. + public static var userSessionsOverviewSecurityRecommendationsSectionInfo: String { + return VectorL10n.tr("Vector", "user_sessions_overview_security_recommendations_section_info") + } + /// Security recommendations + public static var userSessionsOverviewSecurityRecommendationsSectionTitle: String { + return VectorL10n.tr("Vector", "user_sessions_overview_security_recommendations_section_title") + } + /// Verify or sign out from unverified sessions. + public static var userSessionsOverviewSecurityRecommendationsUnverifiedInfo: String { + return VectorL10n.tr("Vector", "user_sessions_overview_security_recommendations_unverified_info") + } + /// Unverified sessions + public static var userSessionsOverviewSecurityRecommendationsUnverifiedTitle: String { + return VectorL10n.tr("Vector", "user_sessions_overview_security_recommendations_unverified_title") + } /// Sessions public static var userSessionsOverviewTitle: String { return VectorL10n.tr("Vector", "user_sessions_overview_title") @@ -8571,6 +8595,10 @@ public class VectorL10n: NSObject { public static var userSessionsSettings: String { return VectorL10n.tr("Vector", "user_sessions_settings") } + /// View all (%d) + public static func userSessionsViewAllAction(_ p1: Int) -> String { + return VectorL10n.tr("Vector", "user_sessions_view_all_action", p1) + } /// If you didn’t sign in to this session, your account may be compromised. public static var userVerificationSessionDetailsAdditionalInformationUntrustedCurrentUser: String { return VectorL10n.tr("Vector", "user_verification_session_details_additional_information_untrusted_current_user") diff --git a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift index a63491e5a..6fba28616 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift @@ -18,20 +18,11 @@ import Foundation /// Represents a user session information struct UserSessionInfo: Identifiable { - /// Delay after which session is considered inactive, 90 days - static let inactiveSessionDurationTreshold: TimeInterval = 90 * 86400 - - // MARK: - Properties - - var id: String { - sessionId - } - /// The session identifier - let sessionId: String + let id: String /// The session display name - let sessionName: String? + let name: String? /// The device type used by the session let deviceType: DeviceType @@ -46,35 +37,10 @@ struct UserSessionInfo: Identifiable { let lastSeenTimestamp: TimeInterval? /// True to indicate that session has been used under `inactiveSessionDurationTreshold` value - let isSessionActive: Bool + let isActive: Bool /// True to indicate that this is current user session - let isCurrentSession: Bool - - // MARK: - Setup - - init(sessionId: String, - sessionName: String?, - deviceType: DeviceType, - isVerified: Bool, - lastSeenIP: String?, - lastSeenTimestamp: TimeInterval?, - isCurrentSession: Bool) { - self.sessionId = sessionId - self.sessionName = sessionName - self.deviceType = deviceType - self.isVerified = isVerified - self.lastSeenIP = lastSeenIP - self.lastSeenTimestamp = lastSeenTimestamp - - if let lastSeenTimestamp = lastSeenTimestamp { - let elapsedTime = Date().timeIntervalSince1970 - lastSeenTimestamp - isSessionActive = elapsedTime < Self.inactiveSessionDurationTreshold - } else { - isSessionActive = true - } - self.isCurrentSession = isCurrentSession - } + let isCurrent: Bool } extension UserSessionInfo: Equatable { diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift index a54915e23..bf49cedb8 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift @@ -136,13 +136,14 @@ struct UserSessionCardViewPreview: View { let viewData: UserSessionCardViewData init(isCurrentSessionInfo: Bool = false) { - let session = UserSessionInfo(sessionId: "alice", - sessionName: "iOS", + let session = UserSessionInfo(id: "alice", + name: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: Date().timeIntervalSince1970 - 100, - isCurrentSession: isCurrentSessionInfo) + isActive: true, + isCurrent: isCurrentSessionInfo) viewData = UserSessionCardViewData(session: session) } diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift index 3fc86306e..339de5806 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift @@ -67,12 +67,12 @@ struct UserSessionCardViewData { extension UserSessionCardViewData { init(session: UserSessionInfo) { - self.init(sessionId: session.sessionId, - sessionDisplayName: session.sessionName, + self.init(sessionId: session.id, + sessionDisplayName: session.name, deviceType: session.deviceType, isVerified: session.isVerified, lastActivityTimestamp: session.lastSeenTimestamp, lastSeenIP: session.lastSeenIP, - isCurrentSessionDisplayMode: session.isCurrentSession) + isCurrentSessionDisplayMode: session.isCurrent) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift index dcb31faef..22b263543 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift @@ -41,21 +41,23 @@ enum MockUserSessionDetailsScreenState: MockScreenState, CaseIterable { let session: UserSessionInfo switch self { case .allSections: - session = UserSessionInfo(sessionId: "session", - sessionName: "iOS", + session = UserSessionInfo(id: "session", + name: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: Date().timeIntervalSince1970 - 100, - isCurrentSession: true) + isActive: true, + isCurrent: true) case .sessionSectionOnly: - session = UserSessionInfo(sessionId: "session", - sessionName: "iOS", + session = UserSessionInfo(id: "session", + name: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: nil, lastSeenTimestamp: Date().timeIntervalSince1970 - 100, - isCurrentSession: false) + isActive: true, + isCurrent: false) } let viewModel = UserSessionDetailsViewModel(session: session) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift index 20debe96b..cf4ddf43c 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift @@ -28,7 +28,7 @@ class UserSessionDetailsViewModelTests: XCTestCase { sessionItems.append(sessionIdItem(sessionId: userSessionInfo.sessionId)) var sections = [UserSessionDetailsSectionViewData]() - sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader, + sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader.uppercased(), footer: VectorL10n.userSessionDetailsSessionSectionFooter, items: sessionItems)) let expectedModel = UserSessionDetailsViewState(sections: sections) @@ -47,7 +47,7 @@ class UserSessionDetailsViewModelTests: XCTestCase { sessionItems.append(sessionIdItem(sessionId: userSessionInfo.sessionId)) var sections = [UserSessionDetailsSectionViewData]() - sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader, + sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader.uppercased(), footer: VectorL10n.userSessionDetailsSessionSectionFooter, items: sessionItems)) @@ -67,14 +67,14 @@ class UserSessionDetailsViewModelTests: XCTestCase { sessionItems.append(sessionIdItem(sessionId: userSessionInfo.sessionId)) var sections = [UserSessionDetailsSectionViewData]() - sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader, + sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader.uppercased(), footer: VectorL10n.userSessionDetailsSessionSectionFooter, items: sessionItems)) var deviceSectionItems = [UserSessionDetailsSectionItemViewData]() deviceSectionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.userSessionDetailsDeviceIpAddress, value: "0.0.0.0")) - sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsDeviceSectionHeader, + sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsDeviceSectionHeader.uppercased(), footer: nil, items: deviceSectionItems)) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift index 66a5d7559..9fe47469d 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift @@ -45,15 +45,15 @@ class UserSessionDetailsViewModel: UserSessionDetailsViewModelType, UserSessionD private func sessionSection(session: UserSessionInfo) -> UserSessionDetailsSectionViewData { var sessionItems = [UserSessionDetailsSectionItemViewData]() - if let sessionName = session.sessionName { + if let sessionName = session.name { sessionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.userSessionDetailsSessionName, value: sessionName)) } sessionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.keyVerificationManuallyVerifyDeviceIdTitle, - value: session.sessionId)) + value: session.id)) - return UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader, + return UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader.uppercased(), footer: VectorL10n.userSessionDetailsSessionSectionFooter, items: sessionItems) } @@ -65,7 +65,7 @@ class UserSessionDetailsViewModel: UserSessionDetailsViewModelType, UserSessionD value: lastSeenIP)) } if deviceSectionItems.count > 0 { - return UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsDeviceSectionHeader, + return UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsDeviceSectionHeader.uppercased(), footer: nil, items: deviceSectionItems) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift index 4830859d0..0cfa0b7a4 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift @@ -41,22 +41,24 @@ enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { let viewModel: UserSessionOverviewViewModel switch self { case .currentSession: - let session = UserSessionInfo(sessionId: "session", - sessionName: "iOS", + let session = UserSessionInfo(id: "session", + name: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: Date().timeIntervalSince1970 - 100, - isCurrentSession: true) + isActive: true, + isCurrent: true) viewModel = UserSessionOverviewViewModel(session: session) case .otherSession: - let session = UserSessionInfo(sessionId: "session", - sessionName: "Mac", + let session = UserSessionInfo(id: "session", + name: "Mac", deviceType: .desktop, isVerified: true, lastSeenIP: "10.0.0.10", lastSeenTimestamp: Date().timeIntervalSince1970 - 100, - isCurrentSession: false) + isActive: true, + isCurrent: false) viewModel = UserSessionOverviewViewModel(session: session) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift index 95261d7c8..42eaaa6c7 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift @@ -27,7 +27,7 @@ class UserSessionOverviewViewModel: UserSessionOverviewViewModelType, UserSessio self.session = session let cardViewData = UserSessionCardViewData(session: session) - let state = UserSessionOverviewViewState(cardViewData: cardViewData, isCurrentSession: session.isCurrentSession) + let state = UserSessionOverviewViewState(cardViewData: cardViewData, isCurrentSession: session.isCurrent) super.init(initialViewState: state) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift index da4e30ba8..da0127e83 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift @@ -18,6 +18,9 @@ import Foundation import MatrixSDK class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { + /// Delay after which session is considered inactive, 90 days + static private let inactiveSessionDurationTreshold: TimeInterval = 90 * 86400 + private let mxSession: MXSession private(set) var overviewData: UserSessionsOverviewData @@ -52,7 +55,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { return overviewData.currentSession } - return overviewData.otherSessions.first(where: { $0.sessionId == sessionId }) + return overviewData.otherSessions.first(where: { $0.id == sessionId }) } // MARK: - Private @@ -85,7 +88,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { var otherSessions: [UserSessionInfo] = [] for session in allSessions { - if session.isCurrentSession { + if session.isCurrent { currentSession = session } else { otherSessions.append(session) @@ -94,7 +97,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { unverifiedSessions.append(session) } - if session.isSessionActive == false { + if session.isActive == false { inactiveSessions.append(session) } } @@ -114,13 +117,20 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { lastSeenTs = TimeInterval(device.lastSeenTs / 1000) } - return UserSessionInfo(sessionId: device.deviceId, - sessionName: device.displayName, + var isSessionActive = true + if let lastSeenTimestamp = lastSeenTs { + let elapsedTime = Date().timeIntervalSince1970 - lastSeenTimestamp + isSessionActive = elapsedTime < Self.inactiveSessionDurationTreshold + } + + return UserSessionInfo(id: device.deviceId, + name: device.displayName, deviceType: .unknown, isVerified: isSessionVerified, lastSeenIP: device.lastSeenIp, lastSeenTimestamp: lastSeenTs, - isCurrentSession: isCurrentSession) + isActive: isSessionActive, + isCurrent: isCurrentSession) } private func deviceInfo(for deviceId: String) -> MXDeviceInfo? { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift index 13167d350..ac863f5ce 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift @@ -28,21 +28,49 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { } init() { - let currentSessionInfo = UserSessionInfo(sessionId: "alice", sessionName: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: nil, isCurrentSession: true) + let currentSession = UserSessionInfo(id: "alice", + name: "iOS", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: nil, + isActive: true, + isCurrent: true) - let unverifiedSessionsInfo: [UserSessionInfo] = [] - - let inactiveSessionsInfo: [UserSessionInfo] = [] - - let otherSessionsInfo: [UserSessionInfo] = [ - UserSessionInfo(sessionId: "1", sessionName: "macOS", deviceType: .desktop, isVerified: true, lastSeenIP: "1.0.0.1", lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000, isCurrentSession: false), - UserSessionInfo(sessionId: "2", sessionName: "Firefox on Windows", deviceType: .web, isVerified: true, lastSeenIP: "2.0.0.2", lastSeenTimestamp: Date().timeIntervalSince1970 - 100, isCurrentSession: false), - UserSessionInfo(sessionId: "3", sessionName: "Android", deviceType: .mobile, isVerified: false, lastSeenIP: "3.0.0.3", lastSeenTimestamp: Date().timeIntervalSince1970 - 10, isCurrentSession: false) + let otherSessions: [UserSessionInfo] = [ + UserSessionInfo(id: "1", + name: "macOS", + deviceType: .desktop, + isVerified: true, + lastSeenIP: "1.0.0.1", + lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000, + isActive: false, + isCurrent: false), + UserSessionInfo(id: "2", + name: "Firefox on Windows", + deviceType: .web, + isVerified: true, + lastSeenIP: "2.0.0.2", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100, + isActive: true, + isCurrent: false), + UserSessionInfo(id: "3", + name: "Android", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "3.0.0.3", + lastSeenTimestamp: Date().timeIntervalSince1970 - 10, + isActive: true, + isCurrent: false) ] - overviewData = UserSessionsOverviewData(currentSession: currentSessionInfo, - unverifiedSessions: unverifiedSessionsInfo, - inactiveSessions: inactiveSessionsInfo, - otherSessions: otherSessionsInfo) + let unverifiedSessions: [UserSessionInfo] = otherSessions.filter { !$0.isVerified } + + let inactiveSessions: [UserSessionInfo] = otherSessions.filter { !$0.isActive } + + overviewData = UserSessionsOverviewData(currentSession: currentSession, + unverifiedSessions: unverifiedSessions, + inactiveSessions: inactiveSessions, + otherSessions: otherSessions) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/SecurityRecommendationCard.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/SecurityRecommendationCard.swift new file mode 100644 index 000000000..090051154 --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/SecurityRecommendationCard.swift @@ -0,0 +1,111 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import SwiftUI + +struct SecurityRecommendationCard: View { + enum Style { + case unverified + case inactive + } + + @Environment(\.theme) var theme: ThemeSwiftUI + + let style: SecurityRecommendationCard.Style + let sessionCount: Int + let action: () -> Void + + var body: some View { + HStack(alignment: .top) { + Image(iconName) + VStack(alignment: .leading, spacing: 16.0) { + VStack(alignment: .leading, spacing: 8.0) { + Text(title) + .font(theme.fonts.calloutSB) + .foregroundColor(theme.colors.primaryContent) + + Text(subtitle) + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.secondaryContent) + } + + Button { + action() + } label: { + Text(buttonTitle) + .font(theme.fonts.body) + } + .foregroundColor(theme.colors.accent) + } + .frame(maxWidth: .infinity, alignment: .leading) + } + .padding(16) + .background(theme.colors.background) + .clipShape(backgroundShape) + .shapedBorder(color: theme.colors.quinaryContent, borderWidth: 1.0, shape: backgroundShape) + } + + private var backgroundShape: RoundedRectangle { + RoundedRectangle(cornerRadius: 8) + } + + private var iconName: String { + switch style { + case .unverified: + return Asset.Images.userSessionsUnverified.name + case .inactive: + return Asset.Images.userSessionsInactive.name + } + } + + private var title: String { + switch style { + case .unverified: + return VectorL10n.userSessionsOverviewSecurityRecommendationsUnverifiedTitle + case .inactive: + return VectorL10n.userSessionsOverviewSecurityRecommendationsInactiveTitle + } + } + + private var subtitle: String { + switch style { + case .unverified: + return VectorL10n.userSessionsOverviewSecurityRecommendationsUnverifiedInfo + case .inactive: + return VectorL10n.userSessionsOverviewSecurityRecommendationsInactiveInfo + } + } + + private var buttonTitle: String { + VectorL10n.userSessionsViewAllAction(sessionCount) + } +} + +struct SecurityRecommendationCard_Previews: PreviewProvider { + static var previews: some View { + body.theme(.light).preferredColorScheme(.light) + body.theme(.dark).preferredColorScheme(.dark) + } + + @ViewBuilder + static var body: some View { + VStack { + SecurityRecommendationCard(style: .unverified, sessionCount: 4, action: { }) + SecurityRecommendationCard(style: .inactive, sessionCount: 100, action: { }) + } + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift index b72301eb0..56bccbbad 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift @@ -69,6 +69,10 @@ struct UserSessionListItemViewData: Identifiable { extension UserSessionListItemViewData { init(session: UserSessionInfo) { - self.init(sessionId: session.sessionId, sessionDisplayName: session.sessionName, deviceType: session.deviceType, isVerified: session.isVerified, lastActivityDate: session.lastSeenTimestamp) + self.init(sessionId: session.id, + sessionDisplayName: session.name, + deviceType: session.deviceType, + isVerified: session.isVerified, + lastActivityDate: session.lastSeenTimestamp) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift index 0d2c5cabe..925ab82b7 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift @@ -19,43 +19,14 @@ import SwiftUI struct UserSessionsOverview: View { @Environment(\.theme) private var theme: ThemeSwiftUI - @ViewBuilder - private var currentSessionsSection: some View { - if let currentSessionViewData = viewModel.viewState.currentSessionViewData { - SwiftUI.Section { - UserSessionCardView(viewData: currentSessionViewData, onVerifyAction: { _ in - viewModel.send(viewAction: .verifyCurrentSession) - }, onViewDetailsAction: { _ in - viewModel.send(viewAction: .viewCurrentSessionDetails) - }) - .padding(.horizontal, 16) - } header: { - Text(VectorL10n.userSessionsOverviewCurrentSessionSectionTitle) - .font(theme.fonts.footnote) - .foregroundColor(theme.colors.secondaryContent) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 16) - .padding(.top, 24) - .padding(.bottom, 11) - } - } - } - - // MARK: Public - @ObservedObject var viewModel: UserSessionsOverviewViewModel.Context var body: some View { ScrollView { - // Security recommendations section - if viewModel.viewState.unverifiedSessionsViewData.isEmpty == false || viewModel.viewState.inactiveSessionsViewData.isEmpty == false { - // TODO: - } + securityRecommendationsSection - // Current session section currentSessionsSection - // Other sessions section if viewModel.viewState.otherSessionsViewData.isEmpty == false { otherSessionsSection } @@ -68,6 +39,69 @@ struct UserSessionsOverview: View { viewModel.send(viewAction: .viewAppeared) } } + + @ViewBuilder + private var securityRecommendationsSection: some View { + if hasSecurityRecommendations { + SwiftUI.Section { + if !viewModel.viewState.unverifiedSessionsViewData.isEmpty { + SecurityRecommendationCard(style: .unverified, + sessionCount: viewModel.viewState.unverifiedSessionsViewData.count) { + viewModel.send(viewAction: .viewAllUnverifiedSessions) + } + } + + if !viewModel.viewState.inactiveSessionsViewData.isEmpty { + SecurityRecommendationCard(style: .inactive, + sessionCount: viewModel.viewState.inactiveSessionsViewData.count) { + viewModel.send(viewAction: .viewAllInactiveSessions) + } + } + } header: { + VStack(alignment: .leading) { + Text(VectorL10n.userSessionsOverviewSecurityRecommendationsSectionTitle) + .textCase(.uppercase) + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.secondaryContent) + .padding(.bottom, 8.0) + + Text(VectorL10n.userSessionsOverviewSecurityRecommendationsSectionInfo) + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.secondaryContent) + .padding(.bottom, 12.0) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.top, 24) + } + .padding(.horizontal, 16) + } + } + + var hasSecurityRecommendations: Bool { + !viewModel.viewState.unverifiedSessionsViewData.isEmpty || !viewModel.viewState.inactiveSessionsViewData.isEmpty + } + + @ViewBuilder + private var currentSessionsSection: some View { + if let currentSessionViewData = viewModel.viewState.currentSessionViewData { + SwiftUI.Section { + UserSessionCardView(viewData: currentSessionViewData, onVerifyAction: { _ in + viewModel.send(viewAction: .verifyCurrentSession) + }, onViewDetailsAction: { _ in + viewModel.send(viewAction: .viewCurrentSessionDetails) + }) + } header: { + Text(VectorL10n.userSessionsOverviewCurrentSessionSectionTitle) + .textCase(.uppercase) + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.secondaryContent) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.bottom, 12.0) + .padding(.top, 24.0) + } + .padding(.horizontal, 16) + } + } private var otherSessionsSection: some View { SwiftUI.Section { @@ -83,17 +117,19 @@ struct UserSessionsOverview: View { } header: { VStack(alignment: .leading) { Text(VectorL10n.userSessionsOverviewOtherSessionsSectionTitle) + .textCase(.uppercase) .font(theme.fonts.footnote) .foregroundColor(theme.colors.secondaryContent) - .padding(.bottom, 10) + .padding(.bottom, 8.0) Text(VectorL10n.userSessionsOverviewOtherSessionsSectionInfo) .font(theme.fonts.footnote) .foregroundColor(theme.colors.secondaryContent) - .padding(.bottom, 11) + .padding(.bottom, 12.0) } - .padding(.horizontal, 16) - .padding(.top, 24) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 16.0) + .padding(.top, 24.0) } } } From daa1f5b671221c55b73e1f7f23d769028a13214b Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 27 Sep 2022 17:44:38 +0300 Subject: [PATCH 081/104] Cleaned up session overview services --- .../MockUserSessionsOverviewScreenState.swift | 3 - .../UserSessionsOverviewService.swift | 29 +------ .../MockUserSessionsOverviewService.swift | 83 +++++++++---------- 3 files changed, 43 insertions(+), 72 deletions(-) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/MockUserSessionsOverviewScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/MockUserSessionsOverviewScreenState.swift index 710c54294..01e53c636 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/MockUserSessionsOverviewScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/MockUserSessionsOverviewScreenState.swift @@ -20,9 +20,6 @@ import SwiftUI /// Using an enum for the screen allows you define the different state cases with /// the relevant associated data for each case. enum MockUserSessionsOverviewScreenState: MockScreenState, CaseIterable { - // A case for each state you want to represent - // with specific, minimal associated data that will allow you - // mock that screen. case verifiedSession /// The associated screen diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift index da0127e83..b2924aedb 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift @@ -82,31 +82,10 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { .sorted { $0.lastSeenTs > $1.lastSeenTs } .map { sessionInfo(from: $0, isCurrentSession: $0.deviceId == mxSession.myDeviceId) } - var currentSession: UserSessionInfo? - var unverifiedSessions: [UserSessionInfo] = [] - var inactiveSessions: [UserSessionInfo] = [] - var otherSessions: [UserSessionInfo] = [] - - for session in allSessions { - if session.isCurrent { - currentSession = session - } else { - otherSessions.append(session) - - if session.isVerified == false { - unverifiedSessions.append(session) - } - - if session.isActive == false { - inactiveSessions.append(session) - } - } - } - - return UserSessionsOverviewData(currentSession: currentSession, - unverifiedSessions: unverifiedSessions, - inactiveSessions: inactiveSessions, - otherSessions: otherSessions) + return UserSessionsOverviewData(currentSession: allSessions.filter(\.isCurrent).first, + unverifiedSessions: allSessions.filter { !$0.isVerified }, + inactiveSessions: allSessions.filter { !$0.isActive }, + otherSessions: allSessions.filter { !$0.isCurrent }) } private func sessionInfo(from device: MXDevice, isCurrentSession: Bool) -> UserSessionInfo { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift index ac863f5ce..d94dbe6c9 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift @@ -28,49 +28,44 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { } init() { - let currentSession = UserSessionInfo(id: "alice", - name: "iOS", - deviceType: .mobile, - isVerified: false, - lastSeenIP: "10.0.0.10", - lastSeenTimestamp: nil, - isActive: true, - isCurrent: true) - - let otherSessions: [UserSessionInfo] = [ - UserSessionInfo(id: "1", - name: "macOS", - deviceType: .desktop, - isVerified: true, - lastSeenIP: "1.0.0.1", - lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000, - isActive: false, - isCurrent: false), - UserSessionInfo(id: "2", - name: "Firefox on Windows", - deviceType: .web, - isVerified: true, - lastSeenIP: "2.0.0.2", - lastSeenTimestamp: Date().timeIntervalSince1970 - 100, - isActive: true, - isCurrent: false), - UserSessionInfo(id: "3", - name: "Android", - deviceType: .mobile, - isVerified: false, - lastSeenIP: "3.0.0.3", - lastSeenTimestamp: Date().timeIntervalSince1970 - 10, - isActive: true, - isCurrent: false) - ] - - let unverifiedSessions: [UserSessionInfo] = otherSessions.filter { !$0.isVerified } - - let inactiveSessions: [UserSessionInfo] = otherSessions.filter { !$0.isActive } - - overviewData = UserSessionsOverviewData(currentSession: currentSession, - unverifiedSessions: unverifiedSessions, - inactiveSessions: inactiveSessions, - otherSessions: otherSessions) + overviewData = UserSessionsOverviewData(currentSession: Self.allSessions.filter(\.isCurrent).first, + unverifiedSessions: Self.allSessions.filter { !$0.isVerified }, + inactiveSessions: Self.allSessions.filter { !$0.isActive }, + otherSessions: Self.allSessions.filter { !$0.isCurrent }) } + + static var allSessions: [UserSessionInfo] = { + [UserSessionInfo(id: "alice", + name: "iOS", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: nil, + isActive: true, + isCurrent: true), + UserSessionInfo(id: "1", + name: "macOS", + deviceType: .desktop, + isVerified: true, + lastSeenIP: "1.0.0.1", + lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000, + isActive: false, + isCurrent: false), + UserSessionInfo(id: "2", + name: "Firefox on Windows", + deviceType: .web, + isVerified: true, + lastSeenIP: "2.0.0.2", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100, + isActive: true, + isCurrent: false), + UserSessionInfo(id: "3", + name: "Android", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "3.0.0.3", + lastSeenTimestamp: Date().timeIntervalSince1970 - 10, + isActive: true, + isCurrent: false)] + }() } From 5494542314f77797e386b30b6226c5b024415a6e Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 28 Sep 2022 12:17:34 +0300 Subject: [PATCH 082/104] Fix tests --- .../Unit/UserSessionDetailsViewModelTests.swift | 13 +++++++------ .../Unit/UserSessionOverviewViewModelTests.swift | 7 ++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift index cf4ddf43c..5949ad6af 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift @@ -25,7 +25,7 @@ class UserSessionDetailsViewModelTests: XCTestCase { lastSeenIP: nil) var sessionItems = [UserSessionDetailsSectionItemViewData]() - sessionItems.append(sessionIdItem(sessionId: userSessionInfo.sessionId)) + sessionItems.append(sessionIdItem(sessionId: userSessionInfo.id)) var sections = [UserSessionDetailsSectionViewData]() sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader.uppercased(), @@ -44,7 +44,7 @@ class UserSessionDetailsViewModelTests: XCTestCase { var sessionItems = [UserSessionDetailsSectionItemViewData]() sessionItems.append(sessionNameItem(sessionName: "session name")) - sessionItems.append(sessionIdItem(sessionId: userSessionInfo.sessionId)) + sessionItems.append(sessionIdItem(sessionId: userSessionInfo.id)) var sections = [UserSessionDetailsSectionViewData]() sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader.uppercased(), @@ -64,7 +64,7 @@ class UserSessionDetailsViewModelTests: XCTestCase { var sessionItems = [UserSessionDetailsSectionItemViewData]() sessionItems.append(sessionNameItem(sessionName: "session name")) - sessionItems.append(sessionIdItem(sessionId: userSessionInfo.sessionId)) + sessionItems.append(sessionIdItem(sessionId: userSessionInfo.id)) var sections = [UserSessionDetailsSectionViewData]() sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader.uppercased(), @@ -91,13 +91,14 @@ class UserSessionDetailsViewModelTests: XCTestCase { lastSeenIP: String?, lastSeenTimestamp: TimeInterval = Date().timeIntervalSince1970, isCurrentSession: Bool = true) -> UserSessionInfo { - UserSessionInfo(sessionId: sessionId, - sessionName: sessionName, + UserSessionInfo(id: sessionId, + name: sessionName, deviceType: deviceType, isVerified: isVerified, lastSeenIP: lastSeenIP, lastSeenTimestamp: lastSeenTimestamp, - isCurrentSession: isCurrentSession) + isActive: true, + isCurrent: isCurrentSession) } private func sessionNameItem(sessionName: String) -> UserSessionDetailsSectionItemViewData { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift index f2498bc56..a3fb37f5f 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift @@ -46,12 +46,13 @@ class UserSessionOverviewViewModelTests: XCTestCase { } private func createUserSessionInfo() -> UserSessionInfo { - UserSessionInfo(sessionId: "session", - sessionName: "iOS", + UserSessionInfo(id: "session", + name: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: Date().timeIntervalSince1970 - 100, - isCurrentSession: true) + isActive: true, + isCurrent: true) } } From cb65feaea0016af7623a769dd26099ec3042653f Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 28 Sep 2022 14:15:51 +0300 Subject: [PATCH 083/104] Fix member modifier warning --- .../Service/MatrixSDK/UserSessionsOverviewService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift index b2924aedb..fc3a37162 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift @@ -19,7 +19,7 @@ import MatrixSDK class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { /// Delay after which session is considered inactive, 90 days - static private let inactiveSessionDurationTreshold: TimeInterval = 90 * 86400 + private static let inactiveSessionDurationTreshold: TimeInterval = 90 * 86400 private let mxSession: MXSession From 1d85ac8df8e4fe2e493a6b27b6810473c104d7f5 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Wed, 28 Sep 2022 18:03:41 +0100 Subject: [PATCH 084/104] Prepare for new sprint --- Config/AppVersion.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index 7e347c68e..4cde3544a 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.9.7 -CURRENT_PROJECT_VERSION = 1.9.7 +MARKETING_VERSION = 1.9.8 +CURRENT_PROJECT_VERSION = 1.9.8 From 836d96ae140914176f25ec48a423cb5787878669 Mon Sep 17 00:00:00 2001 From: aringenbach Date: Thu, 29 Sep 2022 11:10:40 +0200 Subject: [PATCH 085/104] Fix code block background colour --- Riot/Utils/EventFormatter.m | 4 ++-- changelog.d/6778.bugfix | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changelog.d/6778.bugfix diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m index c56381d51..81b69430b 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -461,8 +461,8 @@ static NSString *const kEventFormatterTimeFormat = @"HH:mm"; { calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; - // Use the secondary bg color to set the background color in the default CSS. - NSUInteger bgColor = [MXKTools rgbValueWithColor:ThemeService.shared.theme.headerBackgroundColor]; + // Use the selected bg color to set the code block background color in the default CSS. + NSUInteger bgColor = [MXKTools rgbValueWithColor:ThemeService.shared.theme.selectedBackgroundColor]; self.defaultCSS = [NSString stringWithFormat:@" \ pre,code { \ background-color: #%06lX; \ diff --git a/changelog.d/6778.bugfix b/changelog.d/6778.bugfix new file mode 100644 index 000000000..9721d8cff --- /dev/null +++ b/changelog.d/6778.bugfix @@ -0,0 +1 @@ +Fix code block background colour From f8dccee9d6167ad65b0e6f1ee8a0e8b237d6e45a Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Thu, 29 Sep 2022 15:07:10 +0300 Subject: [PATCH 086/104] Extended device info (PSG-772) (#6766) --- Riot/Assets/en.lproj/Vector.strings | 8 +- Riot/Generated/Strings.swift | 28 +++ .../Common/Extensions/Collection.swift | 24 +++ .../UserSessions/Common/UserAgentParser.swift | 201 +++++++++++++++++ .../UserSessions/Common/UserSessionInfo.swift | 31 ++- .../Common/View/UserSessionCardView.swift | 20 +- .../MockUserSessionDetailsScreenState.swift | 26 ++- .../UserSessionDetailsViewModelTests.swift | 145 ++++++++----- .../UserSessionDetailsViewModel.swift | 71 ++++-- .../MockUserSessionOverviewScreenState.swift | 53 +++-- .../UserSessionOverviewViewModelTests.swift | 7 + .../View/UserSessionOverview.swift | 6 +- .../UserSessionsOverviewCoordinator.swift | 2 +- .../UserSessionsOverviewService.swift | 58 +++-- .../MockUserSessionsOverviewService.swift | 98 ++++++--- RiotTests/UserAgentParserTests.swift | 202 ++++++++++++++++++ changelog.d/pr-6766.change | 1 + 17 files changed, 825 insertions(+), 156 deletions(-) create mode 100644 RiotSwiftUI/Modules/Common/Extensions/Collection.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/Common/UserAgentParser.swift create mode 100644 RiotTests/UserAgentParserTests.swift create mode 100644 changelog.d/pr-6766.change diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index f8b9ded11..380ecd5c7 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2398,12 +2398,18 @@ To enable access, tap Settings> Location and select Always"; "user_session_details_title" = "Session details"; "user_session_details_session_section_header" = "Session"; +"user_session_details_application_section_header" = "Application"; "user_session_details_device_section_header" = "Device"; "user_session_details_session_name" = "Session name"; "user_session_details_session_id" = "Session ID"; "user_session_details_session_section_footer" = "Copy any data by tapping on it and holding it down."; "user_session_details_device_ip_address" = "IP address"; - +"user_session_details_device_ip_location" = "IP location"; +"user_session_details_device_model" = "Model"; +"user_session_details_device_os" = "Operating System"; +"user_session_details_application_name" = "Name"; +"user_session_details_application_version" = "Version"; +"user_session_details_application_url" = "URL"; "user_session_overview_current_session_title" = "Current session"; "user_session_overview_session_title" = "Session"; "user_session_overview_session_details_button_title" = "Session details"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 60f83c1eb..854994c3d 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -8471,10 +8471,38 @@ public class VectorL10n: NSObject { public static var userIdTitle: String { return VectorL10n.tr("Vector", "user_id_title") } + /// Name + public static var userSessionDetailsApplicationName: String { + return VectorL10n.tr("Vector", "user_session_details_application_name") + } + /// Application + public static var userSessionDetailsApplicationSectionHeader: String { + return VectorL10n.tr("Vector", "user_session_details_application_section_header") + } + /// URL + public static var userSessionDetailsApplicationUrl: String { + return VectorL10n.tr("Vector", "user_session_details_application_url") + } + /// Version + public static var userSessionDetailsApplicationVersion: String { + return VectorL10n.tr("Vector", "user_session_details_application_version") + } /// IP address public static var userSessionDetailsDeviceIpAddress: String { return VectorL10n.tr("Vector", "user_session_details_device_ip_address") } + /// IP location + public static var userSessionDetailsDeviceIpLocation: String { + return VectorL10n.tr("Vector", "user_session_details_device_ip_location") + } + /// Model + public static var userSessionDetailsDeviceModel: String { + return VectorL10n.tr("Vector", "user_session_details_device_model") + } + /// Operating System + public static var userSessionDetailsDeviceOs: String { + return VectorL10n.tr("Vector", "user_session_details_device_os") + } /// Device public static var userSessionDetailsDeviceSectionHeader: String { return VectorL10n.tr("Vector", "user_session_details_device_section_header") diff --git a/RiotSwiftUI/Modules/Common/Extensions/Collection.swift b/RiotSwiftUI/Modules/Common/Extensions/Collection.swift new file mode 100644 index 000000000..710e4dc21 --- /dev/null +++ b/RiotSwiftUI/Modules/Common/Extensions/Collection.swift @@ -0,0 +1,24 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +extension Collection { + /// Returns the element at the specified index if it is within bounds, otherwise nil. + subscript(safe index: Index) -> Element? { + indices.contains(index) ? self[index] : nil + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/Common/UserAgentParser.swift b/RiotSwiftUI/Modules/UserSessions/Common/UserAgentParser.swift new file mode 100644 index 000000000..d4fe29466 --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/Common/UserAgentParser.swift @@ -0,0 +1,201 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +struct UserAgent { + let deviceType: DeviceType + let deviceModel: String? + let deviceOS: String? + let clientName: String? + let clientVersion: String? + + static let unknown = UserAgent(deviceType: .unknown, + deviceModel: nil, + deviceOS: nil, + clientName: nil, + clientVersion: nil) +} + +extension UserAgent: Equatable { } + +enum UserAgentParser { + private enum Constants { + static let deviceInfoRegexPattern = "\\((?:[^)(]+|\\((?:[^)(]+|\\([^)(]*\\))*\\))*\\)" + + static let androidKeyword = "; MatrixAndroidSdk2" + static let iosKeyword = "; iOS " + static let desktopKeyword = " Electron/" + static let webKeyword = "Mozilla/" + } + + static func parse(_ userAgent: String) -> UserAgent { + if userAgent.vc_caseInsensitiveContains(Constants.androidKeyword) { + return parseAndroid(userAgent) + } else if userAgent.vc_caseInsensitiveContains(Constants.iosKeyword) { + return parseIOS(userAgent) + } else if userAgent.vc_caseInsensitiveContains(Constants.desktopKeyword) { + return parseDesktop(userAgent) + } else if userAgent.vc_caseInsensitiveContains(Constants.webKeyword) { + return parseWeb(userAgent) + } + return .unknown + } + + // Legacy: Element/1.0.0 (Linux; U; Android 6.0.1; SM-A510F Build/MMB29; Flavour GPlay; MatrixAndroidSdk2 1.0) + // New: Element dbg/1.5.0-dev (Xiaomi Mi 9T; Android 11; RKQ1.200826.002 test-keys; Flavour GooglePlay; MatrixAndroidSdk2 1.5.0) + private static func parseAndroid(_ userAgent: String) -> UserAgent { + var deviceModel: String? + var deviceOS: String? + var clientName: String? + var clientVersion: String? + + let (beforeSlash, afterSlash) = userAgent.splitByFirst("/") + clientName = beforeSlash + if let afterSlash = afterSlash { + let (beforeSpace, afterSpace) = afterSlash.splitByFirst(" ") + clientVersion = beforeSpace + if let afterSpace = afterSpace { + if let deviceInfo = findFirstDeviceInfo(in: afterSpace) { + let deviceInfoComponents = deviceInfo.components(separatedBy: "; ") + let isLegacy = deviceInfoComponents[safe: 0] == "Linux" + if isLegacy { + // find the segment starting with "Android" + if let osSegmentIndex = deviceInfoComponents.firstIndex(where: { $0.hasPrefix("Android") }) { + deviceOS = deviceInfoComponents[safe: osSegmentIndex] + deviceModel = deviceInfoComponents[safe: osSegmentIndex + 1] + } + } else { + deviceModel = deviceInfoComponents[safe: 0] + deviceOS = deviceInfoComponents[safe: 1] + } + } + } + } + + return UserAgent(deviceType: .mobile, + deviceModel: deviceModel, + deviceOS: deviceOS, + clientName: clientName, + clientVersion: clientVersion) + } + + // Legacy: Riot/1.8.21 (iPhone; iOS 15.2; Scale/3.00) + // New: Riot/1.8.21 (iPhone X; iOS 15.2; Scale/3.00) + private static func parseIOS(_ userAgent: String) -> UserAgent { + var deviceModel: String? + var deviceOS: String? + var clientName: String? + var clientVersion: String? + + let (beforeSlash, afterSlash) = userAgent.splitByFirst("/") + clientName = beforeSlash + if let afterSlash = afterSlash { + let (beforeSpace, afterSpace) = afterSlash.splitByFirst(" ") + clientVersion = beforeSpace + if let afterSpace = afterSpace { + if let deviceInfo = findFirstDeviceInfo(in: afterSpace) { + let deviceInfoComponents = deviceInfo.components(separatedBy: "; ") + deviceModel = deviceInfoComponents[safe: 0] + deviceOS = deviceInfoComponents[safe: 1] + } + } + } + + return UserAgent(deviceType: .mobile, + deviceModel: deviceModel, + deviceOS: deviceOS, + clientName: clientName, + clientVersion: clientVersion) + } + + // Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) ElementNightly/2022091301 Chrome/104.0.5112.102 Electron/20.1.1 Safari/537.36 + private static func parseDesktop(_ userAgent: String) -> UserAgent { + var deviceOS: String? + let browserName = browserName(for: userAgent) + + if let deviceInfo = findFirstDeviceInfo(in: userAgent) { + let deviceInfoComponents = deviceInfo.components(separatedBy: "; ") + deviceOS = deviceInfoComponents[safe: 1]?.hasPrefix("Android") == true ? deviceInfoComponents[safe: 1] : deviceInfoComponents.first + } + + return UserAgent(deviceType: .desktop, + deviceModel: browserName, + deviceOS: deviceOS, + clientName: nil, + clientVersion: nil) + } + + // Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 + private static func parseWeb(_ userAgent: String) -> UserAgent { + let desktopUserAgent = parseDesktop(userAgent) + + return UserAgent(deviceType: .web, + deviceModel: desktopUserAgent.deviceModel, + deviceOS: desktopUserAgent.deviceOS, + clientName: desktopUserAgent.clientName, + clientVersion: desktopUserAgent.clientVersion) + } + + private static func findFirstDeviceInfo(in string: String) -> String? { + guard let regex = try? NSRegularExpression(pattern: Constants.deviceInfoRegexPattern, + options: .caseInsensitive) else { + return nil + } + var range = regex.rangeOfFirstMatch(in: string, range: NSRange(string.startIndex..., in: string)) + if range.location != NSNotFound { + range.location += 1 + range.length -= 2 + return string[range] + } + return nil + } + + private static func browserName(for userAgent: String) -> String? { + let components = userAgent.components(separatedBy: " ") + if components.last?.hasPrefix("Firefox") == true { + return "Firefox" + } else if components.last?.hasPrefix("Safari") == true + && components[safe:components.count - 2]?.hasPrefix("Mobile") == true { + // mobile browser + let possibleBrowserName = components[safe:components.count - 3]?.components(separatedBy: "/").first + return possibleBrowserName == "Version" ? "Safari" : possibleBrowserName + } else if components.last?.hasPrefix("Safari") == true && components[safe:components.count - 2]?.hasPrefix("Version") == true { + return "Safari" + } else { + // regular browser + return components[safe:components.count - 2]?.components(separatedBy: "/").first + } + } +} + +private extension String { + subscript(_ range: NSRange) -> String { + let start = index(startIndex, offsetBy: range.lowerBound) + let end = index(startIndex, offsetBy: range.upperBound) + let subString = self[start.. (String?, String?) { + guard let delimiterIndex = firstIndex(of: delimiter) else { + return (nil, nil) + } + let before = String(prefix(upTo: delimiterIndex)) + let after = String(suffix(from: index(after: delimiterIndex))) + return (before, after) + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift index 6fba28616..5bfbe2332 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift @@ -35,16 +35,41 @@ struct UserSessionInfo: Identifiable { /// Last time the session was active let lastSeenTimestamp: TimeInterval? - + + // MARK: - Application Properties + + /// Application name used by the session + let applicationName: String? + + /// Application version used by the session + let applicationVersion: String? + + /// Application URL used by the session. Only applicable for web sessions. + let applicationURL: String? + + // MARK: - Device Properties + + /// Device model + let deviceModel: String? + + /// Device OS + let deviceOS: String? + + /// Last seen IP location + let lastSeenIPLocation: String? + + /// Device name + let deviceName: String? + /// True to indicate that session has been used under `inactiveSessionDurationTreshold` value let isActive: Bool - + /// True to indicate that this is current user session let isCurrent: Bool } extension UserSessionInfo: Equatable { static func == (lhs: UserSessionInfo, rhs: UserSessionInfo) -> Bool { - return lhs.id == rhs.id + lhs.id == rhs.id } } diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift index bf49cedb8..3736322ef 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift @@ -134,17 +134,23 @@ struct UserSessionCardViewPreview: View { @Environment(\.theme) var theme: ThemeSwiftUI let viewData: UserSessionCardViewData - - init(isCurrentSessionInfo: Bool = false) { + + init(isCurrent: Bool = false) { let session = UserSessionInfo(id: "alice", name: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", - lastSeenTimestamp: Date().timeIntervalSince1970 - 100, + lastSeenTimestamp: nil, + applicationName: "Element iOS", + applicationVersion: "1.0.0", + applicationURL: nil, + deviceModel: nil, + deviceOS: "iOS 15.5", + lastSeenIPLocation: nil, + deviceName: "My iPhone", isActive: true, - isCurrent: isCurrentSessionInfo) - + isCurrent: isCurrent) viewData = UserSessionCardViewData(session: session) } @@ -161,8 +167,8 @@ struct UserSessionCardViewPreview: View { struct UserSessionCardView_Previews: PreviewProvider { static var previews: some View { Group { - UserSessionCardViewPreview(isCurrentSessionInfo: true).theme(.light).preferredColorScheme(.light) - UserSessionCardViewPreview(isCurrentSessionInfo: true).theme(.dark).preferredColorScheme(.dark) + UserSessionCardViewPreview(isCurrent: true).theme(.light).preferredColorScheme(.light) + UserSessionCardViewPreview(isCurrent: true).theme(.dark).preferredColorScheme(.dark) UserSessionCardViewPreview().theme(.light).preferredColorScheme(.light) UserSessionCardViewPreview().theme(.dark).preferredColorScheme(.dark) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift index 22b263543..37233e141 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift @@ -41,21 +41,35 @@ enum MockUserSessionDetailsScreenState: MockScreenState, CaseIterable { let session: UserSessionInfo switch self { case .allSections: - session = UserSessionInfo(id: "session", + session = UserSessionInfo(id: "alice", name: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", - lastSeenTimestamp: Date().timeIntervalSince1970 - 100, + lastSeenTimestamp: nil, + applicationName: "Element iOS", + applicationVersion: "1.0.0", + applicationURL: nil, + deviceModel: nil, + deviceOS: "iOS 15.5", + lastSeenIPLocation: nil, + deviceName: "My iPhone", isActive: true, isCurrent: true) case .sessionSectionOnly: - session = UserSessionInfo(id: "session", - name: "iOS", + session = UserSessionInfo(id: "3", + name: "Android", deviceType: .mobile, isVerified: false, - lastSeenIP: nil, - lastSeenTimestamp: Date().timeIntervalSince1970 - 100, + lastSeenIP: "3.0.0.3", + lastSeenTimestamp: Date().timeIntervalSince1970 - 10, + applicationName: "Element Android", + applicationVersion: "1.0.0", + applicationURL: nil, + deviceModel: nil, + deviceOS: "Android 4.0", + lastSeenIPLocation: nil, + deviceName: "My Phone", isActive: true, isCurrent: false) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift index 5949ad6af..184725e1e 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift @@ -20,17 +20,20 @@ import XCTest class UserSessionDetailsViewModelTests: XCTestCase { func test_whenSessionNameAndLastSeenIPNil_viewStateCorrect() { - let userSessionInfo = createUserSessionInfo(sessionId: "session", - sessionName: nil, + let userSessionInfo = createUserSessionInfo(id: "session", + name: nil, lastSeenIP: nil) + + let sessionItems = [ + sessionIdItem(sessionId: "session") + ] - var sessionItems = [UserSessionDetailsSectionItemViewData]() - sessionItems.append(sessionIdItem(sessionId: userSessionInfo.id)) - - var sections = [UserSessionDetailsSectionViewData]() - sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader.uppercased(), - footer: VectorL10n.userSessionDetailsSessionSectionFooter, - items: sessionItems)) + let sections = [ + UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader.uppercased(), + footer: VectorL10n.userSessionDetailsSessionSectionFooter, + items: sessionItems) + ] + let expectedModel = UserSessionDetailsViewState(sections: sections) let sut = UserSessionDetailsViewModel(session: userSessionInfo) @@ -38,18 +41,20 @@ class UserSessionDetailsViewModelTests: XCTestCase { } func test_whenSessionNameNotNilLastSeenIPNil_viewStateCorrect() { - let userSessionInfo = createUserSessionInfo(sessionId: "session", - sessionName: "session name", + let userSessionInfo = createUserSessionInfo(id: "session", + name: "session name", lastSeenIP: nil) - - var sessionItems = [UserSessionDetailsSectionItemViewData]() - sessionItems.append(sessionNameItem(sessionName: "session name")) - sessionItems.append(sessionIdItem(sessionId: userSessionInfo.id)) - - var sections = [UserSessionDetailsSectionViewData]() - sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader.uppercased(), - footer: VectorL10n.userSessionDetailsSessionSectionFooter, - items: sessionItems)) + + let sessionItems = [ + sessionNameItem(sessionName: "session name"), + sessionIdItem(sessionId: "session") + ] + + let sections = [ + UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader.uppercased(), + footer: VectorL10n.userSessionDetailsSessionSectionFooter, + items: sessionItems) + ] let expectedModel = UserSessionDetailsViewState(sections: sections) let sut = UserSessionDetailsViewModel(session: userSessionInfo) @@ -58,56 +63,98 @@ class UserSessionDetailsViewModelTests: XCTestCase { } func test_whenUserSessionInfoContainsAllValues_viewStateCorrect() { - let userSessionInfo = createUserSessionInfo(sessionId: "session", - sessionName: "session name", - lastSeenIP: "0.0.0.0") + let userSessionInfo = createUserSessionInfo(id: "session", + name: "session name", + lastSeenIP: "0.0.0.0", + applicationName: "Element iOS", + applicationVersion: "1.0.0") - var sessionItems = [UserSessionDetailsSectionItemViewData]() - sessionItems.append(sessionNameItem(sessionName: "session name")) - sessionItems.append(sessionIdItem(sessionId: userSessionInfo.id)) - - var sections = [UserSessionDetailsSectionViewData]() - sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader.uppercased(), - footer: VectorL10n.userSessionDetailsSessionSectionFooter, - items: sessionItems)) - - var deviceSectionItems = [UserSessionDetailsSectionItemViewData]() - deviceSectionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.userSessionDetailsDeviceIpAddress, - value: "0.0.0.0")) - sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsDeviceSectionHeader.uppercased(), - footer: nil, - items: deviceSectionItems)) + let sessionItems = [ + sessionNameItem(sessionName: "session name"), + sessionIdItem(sessionId: "session") + ] + let appItems = [ + appNameItem(appName: "Element iOS"), + appVersionItem(appVersion: "1.0.0") + ] + let deviceItems = [ + ipAddressItem(ipAddress: "0.0.0.0") + ] + + let sections = [ + UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader.uppercased(), + footer: VectorL10n.userSessionDetailsSessionSectionFooter, + items: sessionItems), + UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsApplicationSectionHeader.uppercased(), + footer: nil, + items: appItems), + UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsDeviceSectionHeader.uppercased(), + footer: nil, + items: deviceItems) + ] let expectedModel = UserSessionDetailsViewState(sections: sections) let sut = UserSessionDetailsViewModel(session: userSessionInfo) XCTAssertEqual(sut.state, expectedModel) } + + // MARK: - Private - private func createUserSessionInfo(sessionId: String, - sessionName: String?, + private func createUserSessionInfo(id: String, + name: String?, deviceType: DeviceType = .mobile, isVerified: Bool = false, lastSeenIP: String?, lastSeenTimestamp: TimeInterval = Date().timeIntervalSince1970, - isCurrentSession: Bool = true) -> UserSessionInfo { - UserSessionInfo(id: sessionId, - name: sessionName, + applicationName: String? = nil, + applicationVersion: String? = nil, + applicationURL: String? = nil, + deviceModel: String? = nil, + deviceOS: String? = nil, + lastSeenIPLocation: String? = nil, + deviceName: String? = nil, + isActive: Bool = true, + isCurrent: Bool = true) -> UserSessionInfo { + UserSessionInfo(id: id, + name: name, deviceType: deviceType, isVerified: isVerified, lastSeenIP: lastSeenIP, lastSeenTimestamp: lastSeenTimestamp, - isActive: true, - isCurrent: isCurrentSession) + applicationName: applicationName, + applicationVersion: applicationVersion, + applicationURL: applicationURL, + deviceModel: deviceModel, + deviceOS: deviceOS, + lastSeenIPLocation: lastSeenIPLocation, + deviceName: deviceName, + isActive: isActive, + isCurrent: isCurrent) } private func sessionNameItem(sessionName: String) -> UserSessionDetailsSectionItemViewData { - UserSessionDetailsSectionItemViewData(title: VectorL10n.userSessionDetailsSessionName, - value: sessionName) + .init(title: VectorL10n.userSessionDetailsSessionName, + value: sessionName) } private func sessionIdItem(sessionId: String) -> UserSessionDetailsSectionItemViewData { - UserSessionDetailsSectionItemViewData(title: VectorL10n.keyVerificationManuallyVerifyDeviceIdTitle, - value: sessionId) + .init(title: VectorL10n.keyVerificationManuallyVerifyDeviceIdTitle, + value: sessionId) + } + + private func appNameItem(appName: String) -> UserSessionDetailsSectionItemViewData { + .init(title: VectorL10n.userSessionDetailsApplicationName, + value: appName) + } + + private func appVersionItem(appVersion: String) -> UserSessionDetailsSectionItemViewData { + .init(title: VectorL10n.userSessionDetailsApplicationVersion, + value: appVersion) + } + + private func ipAddressItem(ipAddress: String) -> UserSessionDetailsSectionItemViewData { + .init(title: VectorL10n.userSessionDetailsDeviceIpAddress, + value: ipAddress) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift index 9fe47469d..475f3bb5f 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift @@ -32,8 +32,12 @@ class UserSessionDetailsViewModel: UserSessionDetailsViewModelType, UserSessionD private func updateViewState(session: UserSessionInfo) { var sections = [UserSessionDetailsSectionViewData]() - + sections.append(sessionSection(session: session)) + + if let applicationSection = applicationSection(session: session) { + sections.append(applicationSection) + } if let deviceSection = deviceSection(session: session) { sections.append(deviceSection) @@ -43,31 +47,68 @@ class UserSessionDetailsViewModel: UserSessionDetailsViewModelType, UserSessionD } private func sessionSection(session: UserSessionInfo) -> UserSessionDetailsSectionViewData { - var sessionItems = [UserSessionDetailsSectionItemViewData]() - + var sessionItems: [UserSessionDetailsSectionItemViewData] = [] + if let sessionName = session.name { - sessionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.userSessionDetailsSessionName, - value: sessionName)) + sessionItems.append(.init(title: VectorL10n.userSessionDetailsSessionName, + value: sessionName)) } - sessionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.keyVerificationManuallyVerifyDeviceIdTitle, - value: session.id)) + sessionItems.append(.init(title: VectorL10n.keyVerificationManuallyVerifyDeviceIdTitle, + value: session.id)) - return UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader.uppercased(), - footer: VectorL10n.userSessionDetailsSessionSectionFooter, - items: sessionItems) + return .init(header: VectorL10n.userSessionDetailsSessionSectionHeader.uppercased(), + footer: VectorL10n.userSessionDetailsSessionSectionFooter, + items: sessionItems) + } + + private func applicationSection(session: UserSessionInfo) -> UserSessionDetailsSectionViewData? { + var sessionItems: [UserSessionDetailsSectionItemViewData] = [] + + if let name = session.applicationName { + sessionItems.append(.init(title: VectorL10n.userSessionDetailsApplicationName, + value: name)) + } + if let version = session.applicationVersion { + sessionItems.append(.init(title: VectorL10n.userSessionDetailsApplicationVersion, + value: version)) + } + if let url = session.applicationURL { + sessionItems.append(.init(title: VectorL10n.userSessionDetailsApplicationUrl, + value: url)) + } + + guard !sessionItems.isEmpty else { + return nil + } + return .init(header: VectorL10n.userSessionDetailsApplicationSectionHeader.uppercased(), + footer: nil, + items: sessionItems) } private func deviceSection(session: UserSessionInfo) -> UserSessionDetailsSectionViewData? { var deviceSectionItems = [UserSessionDetailsSectionItemViewData]() + + if let model = session.deviceModel { + deviceSectionItems.append(.init(title: VectorL10n.userSessionDetailsDeviceModel, + value: model)) + } + if let deviceOS = session.deviceOS { + deviceSectionItems.append(.init(title: VectorL10n.userSessionDetailsDeviceOs, + value: deviceOS)) + } if let lastSeenIP = session.lastSeenIP { - deviceSectionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.userSessionDetailsDeviceIpAddress, - value: lastSeenIP)) + deviceSectionItems.append(.init(title: VectorL10n.userSessionDetailsDeviceIpAddress, + value: lastSeenIP)) + } + if let lastSeenIPLocation = session.lastSeenIPLocation { + deviceSectionItems.append(.init(title: VectorL10n.userSessionDetailsDeviceIpLocation, + value: lastSeenIPLocation)) } if deviceSectionItems.count > 0 { - return UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsDeviceSectionHeader.uppercased(), - footer: nil, - items: deviceSectionItems) + return .init(header: VectorL10n.userSessionDetailsDeviceSectionHeader.uppercased(), + footer: nil, + items: deviceSectionItems) } return nil } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift index 0cfa0b7a4..aa0d437b0 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift @@ -38,30 +38,43 @@ enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { /// Generate the view struct for the screen state. var screenView: ([Any], AnyView) { - let viewModel: UserSessionOverviewViewModel + let session: UserSessionInfo switch self { case .currentSession: - let session = UserSessionInfo(id: "session", - name: "iOS", - deviceType: .mobile, - isVerified: false, - lastSeenIP: "10.0.0.10", - lastSeenTimestamp: Date().timeIntervalSince1970 - 100, - isActive: true, - isCurrent: true) - viewModel = UserSessionOverviewViewModel(session: session) + session = UserSessionInfo(id: "alice", + name: "iOS", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: nil, + applicationName: "Element iOS", + applicationVersion: "1.0.0", + applicationURL: nil, + deviceModel: nil, + deviceOS: "iOS 15.5", + lastSeenIPLocation: nil, + deviceName: "My iPhone", + isActive: true, + isCurrent: true) case .otherSession: - let session = UserSessionInfo(id: "session", - name: "Mac", - deviceType: .desktop, - isVerified: true, - lastSeenIP: "10.0.0.10", - lastSeenTimestamp: Date().timeIntervalSince1970 - 100, - isActive: true, - isCurrent: false) - viewModel = UserSessionOverviewViewModel(session: session) + session = UserSessionInfo(id: "1", + name: "macOS", + deviceType: .desktop, + isVerified: true, + lastSeenIP: "1.0.0.1", + lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000, + applicationName: "Element MacOS", + applicationVersion: "1.0.0", + applicationURL: nil, + deviceModel: nil, + deviceOS: "macOS 12.5.1", + lastSeenIPLocation: nil, + deviceName: "My Mac", + isActive: false, + isCurrent: false) } - + + let viewModel = UserSessionOverviewViewModel(session: session) // can simulate service and viewModel actions here if needs be. return ([viewModel], AnyView(UserSessionOverview(viewModel: viewModel.context))) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift index a3fb37f5f..a89e3f96d 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift @@ -52,6 +52,13 @@ class UserSessionOverviewViewModelTests: XCTestCase { isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: Date().timeIntervalSince1970 - 100, + applicationName: "Element", + applicationVersion: "1.9.7", + applicationURL: nil, + deviceModel: "iPhone XS", + deviceOS: "iOS 15.5", + lastSeenIPLocation: nil, + deviceName: "Mobile", isActive: true, isCurrent: true) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift index ec0f4dca3..8ebf47abc 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift @@ -26,7 +26,7 @@ struct UserSessionOverview: View { UserSessionCardView(viewData: viewModel.viewState.cardViewData, onVerifyAction: { _ in viewModel.send(viewAction: .verifyCurrentSession) }, - onViewDetailsAction: { _ in + onViewDetailsAction: { _ in viewModel.send(viewAction: .viewSessionDetails) }) .padding(16) @@ -39,8 +39,8 @@ struct UserSessionOverview: View { .background(theme.colors.system.ignoresSafeArea()) .frame(maxHeight: .infinity) .navigationTitle(viewModel.viewState.isCurrentSession ? - VectorL10n.userSessionOverviewCurrentSessionTitle : - VectorL10n.userSessionOverviewSessionTitle) + VectorL10n.userSessionOverviewCurrentSessionTitle : + VectorL10n.userSessionOverviewSessionTitle) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift index ac7521dcb..c6316569d 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift @@ -102,7 +102,7 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { private func showCurrentSessionOverview(session: UserSessionInfo) { completion?(.openSessionOverview(session: session)) } - + private func showUserSessionOverview(session: UserSessionInfo) { completion?(.openSessionOverview(session: session)) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift index fc3a37162..874e5dfa1 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift @@ -76,7 +76,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { } return sessionInfo(from: device, isCurrentSession: true) } - + private func sessionsOverviewData(from devices: [MXDevice]) -> UserSessionsOverviewData { let allSessions = devices .sorted { $0.lastSeenTs > $1.lastSeenTs } @@ -90,24 +90,25 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { private func sessionInfo(from device: MXDevice, isCurrentSession: Bool) -> UserSessionInfo { let isSessionVerified = deviceInfo(for: device.deviceId)?.trustLevel.isVerified ?? false - - var lastSeenTs: TimeInterval? - if device.lastSeenTs > 0 { - lastSeenTs = TimeInterval(device.lastSeenTs / 1000) - } - + + let eventType = kMXAccountDataTypeClientInformation + "." + device.deviceId + let appData = mxSession.accountData.accountData(forEventType: eventType) + var userAgent: UserAgent? var isSessionActive = true - if let lastSeenTimestamp = lastSeenTs { - let elapsedTime = Date().timeIntervalSince1970 - lastSeenTimestamp + + if let lastSeenUserAgent = device.lastSeenUserAgent { + userAgent = UserAgentParser.parse(lastSeenUserAgent) + } + + if device.lastSeenTs > 0 { + let elapsedTime = Date().timeIntervalSince1970 - TimeInterval(device.lastSeenTs / 1000) isSessionActive = elapsedTime < Self.inactiveSessionDurationTreshold } - - return UserSessionInfo(id: device.deviceId, - name: device.displayName, - deviceType: .unknown, - isVerified: isSessionVerified, - lastSeenIP: device.lastSeenIp, - lastSeenTimestamp: lastSeenTs, + + return UserSessionInfo(withDevice: device, + applicationData: appData as? [String: String], + userAgent: userAgent, + isSessionVerified: isSessionVerified, isActive: isSessionActive, isCurrent: isCurrentSession) } @@ -120,3 +121,28 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { return mxSession.crypto.device(withDeviceId: deviceId, ofUser: userId) } } + +extension UserSessionInfo { + init(withDevice device: MXDevice, + applicationData: [String: String]?, + userAgent: UserAgent?, + isSessionVerified: Bool, + isActive: Bool, + isCurrent: Bool) { + self.init(id: device.deviceId, + name: device.displayName, + deviceType: userAgent?.deviceType ?? .unknown, + isVerified: isSessionVerified, + lastSeenIP: device.lastSeenIp, + lastSeenTimestamp: device.lastSeenTs > 0 ? TimeInterval(device.lastSeenTs / 1000) : nil, + applicationName: applicationData?["name"], + applicationVersion: applicationData?["version"], + applicationURL: applicationData?["url"], + deviceModel: userAgent?.deviceModel, + deviceOS: userAgent?.deviceOS, + lastSeenIPLocation: nil, + deviceName: userAgent?.clientName, + isActive: isActive, + isCurrent: isCurrent) + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift index d94dbe6c9..f9724f25e 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift @@ -17,7 +17,7 @@ import Foundation class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { - var overviewData: UserSessionsOverviewData + let overviewData: UserSessionsOverviewData func updateOverviewData(completion: @escaping (Result) -> Void) { completion(.success(overviewData)) @@ -34,38 +34,66 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { otherSessions: Self.allSessions.filter { !$0.isCurrent }) } - static var allSessions: [UserSessionInfo] = { - [UserSessionInfo(id: "alice", - name: "iOS", - deviceType: .mobile, - isVerified: false, - lastSeenIP: "10.0.0.10", - lastSeenTimestamp: nil, - isActive: true, - isCurrent: true), - UserSessionInfo(id: "1", - name: "macOS", - deviceType: .desktop, - isVerified: true, - lastSeenIP: "1.0.0.1", - lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000, - isActive: false, - isCurrent: false), - UserSessionInfo(id: "2", - name: "Firefox on Windows", - deviceType: .web, - isVerified: true, - lastSeenIP: "2.0.0.2", - lastSeenTimestamp: Date().timeIntervalSince1970 - 100, - isActive: true, - isCurrent: false), - UserSessionInfo(id: "3", - name: "Android", - deviceType: .mobile, - isVerified: false, - lastSeenIP: "3.0.0.3", - lastSeenTimestamp: Date().timeIntervalSince1970 - 10, - isActive: true, - isCurrent: false)] - }() + static let allSessions = [ + UserSessionInfo(id: "alice", + name: "iOS", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: nil, + applicationName: "Element iOS", + applicationVersion: "1.0.0", + applicationURL: nil, + deviceModel: nil, + deviceOS: "iOS 15.5", + lastSeenIPLocation: nil, + deviceName: "My iPhone", + isActive: true, + isCurrent: true), + UserSessionInfo(id: "1", + name: "macOS", + deviceType: .desktop, + isVerified: true, + lastSeenIP: "1.0.0.1", + lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000, + applicationName: "Element MacOS", + applicationVersion: "1.0.0", + applicationURL: nil, + deviceModel: nil, + deviceOS: "macOS 12.5.1", + lastSeenIPLocation: nil, + deviceName: "My Mac", + isActive: false, + isCurrent: false), + UserSessionInfo(id: "2", + name: "Firefox on Windows", + deviceType: .web, + isVerified: true, + lastSeenIP: "2.0.0.2", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100, + applicationName: "Element Web", + applicationVersion: "1.0.0", + applicationURL: nil, + deviceModel: nil, + deviceOS: "Windows 10", + lastSeenIPLocation: nil, + deviceName: "My Windows", + isActive: true, + isCurrent: false), + UserSessionInfo(id: "3", + name: "Android", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "3.0.0.3", + lastSeenTimestamp: Date().timeIntervalSince1970 - 10, + applicationName: "Element Android", + applicationVersion: "1.0.0", + applicationURL: nil, + deviceModel: nil, + deviceOS: "Android 4.0", + lastSeenIPLocation: nil, + deviceName: "My Phone", + isActive: true, + isCurrent: false) + ] } diff --git a/RiotTests/UserAgentParserTests.swift b/RiotTests/UserAgentParserTests.swift new file mode 100644 index 000000000..d68306d67 --- /dev/null +++ b/RiotTests/UserAgentParserTests.swift @@ -0,0 +1,202 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +@testable import Element + +class UserAgentParserTests: XCTestCase { + + func testAndroidUserAgents() throws { + let uaStrings = [ + // New User Agent Implementation + "Element dbg/1.5.0-dev (Xiaomi Mi 9T; Android 11; RKQ1.200826.002 test-keys; Flavour GooglePlay; MatrixAndroidSdk2 1.5.2)", + "Element/1.5.0 (Samsung SM-G960F; Android 6.0.1; RKQ1.200826.002; Flavour FDroid; MatrixAndroidSdk2 1.5.2)", + "Element/1.5.0 (Google Nexus 5; Android 7.0; RKQ1.200826.002 test test; Flavour FDroid; MatrixAndroidSdk2 1.5.2)", + // Legacy User Agent Implementation + "Element/1.0.0 (Linux; U; Android 6.0.1; SM-A510F Build/MMB29; Flavour GPlay; MatrixAndroidSdk2 1.0)", + "Element/1.0.0 (Linux; Android 7.0; SM-G610M Build/NRD90M; Flavour GPlay; MatrixAndroidSdk2 1.0)" + ] + let userAgents = uaStrings.map { UserAgentParser.parse($0) } + + let expected = [ + UserAgent(deviceType: .mobile, + deviceModel: "Xiaomi Mi 9T", + deviceOS: "Android 11", + clientName: "Element dbg", + clientVersion: "1.5.0-dev"), + UserAgent(deviceType: .mobile, + deviceModel: "Samsung SM-G960F", + deviceOS: "Android 6.0.1", + clientName: "Element", + clientVersion: "1.5.0"), + UserAgent(deviceType: .mobile, + deviceModel: "Google Nexus 5", + deviceOS: "Android 7.0", + clientName: "Element", + clientVersion: "1.5.0"), + UserAgent(deviceType: .mobile, + deviceModel: "SM-A510F Build/MMB29", + deviceOS: "Android 6.0.1", + clientName: "Element", + clientVersion: "1.0.0"), + UserAgent(deviceType: .mobile, + deviceModel: "SM-G610M Build/NRD90M", + deviceOS: "Android 7.0", + clientName: "Element", + clientVersion: "1.0.0") + ] + + XCTAssertEqual(userAgents, expected) + } + + func testIOSUserAgents() throws { + let uaStrings = [ + // New User Agent Implementation + "Element/1.9.8 (iPhone X; iOS 15.2; Scale/3.00)", + "Element/1.9.9 (iPhone XS; iOS 15.5; Scale/3.00)", + "Element/1.9.7 (iPad Pro (12.9-inch) (3rd generation); iOS 15.5; Scale/3.00)", + // Legacy User Agent Implementation + "Element/1.8.21 (iPhone; iOS 15.0; Scale/2.00)", + "Element/1.8.19 (iPhone; iOS 15.2; Scale/3.00)", + // Simulator User Agent + "Element/1.9.7 (Simulator (iPhone 13 Pro Max); iOS 15.5; Scale/3.00)" + ] + let userAgents = uaStrings.map { UserAgentParser.parse($0) } + + let expected = [ + UserAgent(deviceType: .mobile, + deviceModel: "iPhone X", + deviceOS: "iOS 15.2", + clientName: "Element", + clientVersion: "1.9.8"), + UserAgent(deviceType: .mobile, + deviceModel: "iPhone XS", + deviceOS: "iOS 15.5", + clientName: "Element", + clientVersion: "1.9.9"), + UserAgent(deviceType: .mobile, + deviceModel: "iPad Pro (12.9-inch) (3rd generation)", + deviceOS: "iOS 15.5", + clientName: "Element", + clientVersion: "1.9.7"), + UserAgent(deviceType: .mobile, + deviceModel: "iPhone", + deviceOS: "iOS 15.0", + clientName: "Element", + clientVersion: "1.8.21"), + UserAgent(deviceType: .mobile, + deviceModel: "iPhone", + deviceOS: "iOS 15.2", + clientName: "Element", + clientVersion: "1.8.19"), + UserAgent(deviceType: .mobile, + deviceModel: "Simulator (iPhone 13 Pro Max)", + deviceOS: "iOS 15.5", + clientName: "Element", + clientVersion: "1.9.7") + ] + + XCTAssertEqual(userAgents, expected) + } + + func testDesktopUserAgents() { + let uaStrings = [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) ElementNightly/2022091301 Chrome/104.0.5112.102 Electron/20.1.1 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) ElementNightly/2022091301 Chrome/104.0.5112.102 Electron/20.1.1 Safari/537.36" + ] + let userAgents = uaStrings.map { UserAgentParser.parse($0) } + + let expected = [ + UserAgent(deviceType: .desktop, + deviceModel: "Electron", + deviceOS: "Macintosh", + clientName: nil, + clientVersion: nil), + UserAgent(deviceType: .desktop, + deviceModel: "Electron", + deviceOS: "Windows NT 10.0", + clientName: nil, + clientVersion: nil) + ] + + XCTAssertEqual(userAgents, expected) + } + + func testWebUserAgents() throws { + let uaStrings = [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:39.0) Gecko/20100101 Firefox/39.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/600.3.18 (KHTML, like Gecko) Version/8.0.3 Safari/600.3.18", + "Mozilla/5.0 (Linux; Android 9; SM-G973U Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36" + ] + let userAgents = uaStrings.map { UserAgentParser.parse($0) } + + let expected = [ + UserAgent(deviceType: .web, + deviceModel: "Chrome", + deviceOS: "Macintosh", + clientName: nil, + clientVersion: nil), + UserAgent(deviceType: .web, + deviceModel: "Chrome", + deviceOS: "Windows NT 10.0", + clientName: nil, + clientVersion: nil), + UserAgent(deviceType: .web, + deviceModel: "Firefox", + deviceOS: "Macintosh", + clientName: nil, + clientVersion: nil), + UserAgent(deviceType: .web, + deviceModel: "Safari", + deviceOS: "Macintosh", + clientName: nil, + clientVersion: nil), + UserAgent(deviceType: .web, + deviceModel: "Chrome", + deviceOS: "Android 9", + clientName: nil, + clientVersion: nil) + ] + + XCTAssertEqual(userAgents, expected) + } + + func testInvalidUserAgents() throws { + let uaStrings = [ + "Element (iPhone X; OS 15.2; 3.00)", + "Element/1.9.9; iOS", + "Element/1.9.7 Android", + "Element/1.9.9; iOS " + ] + let userAgents = uaStrings.map { UserAgentParser.parse($0) } + + let expected = [ + .unknown, + .unknown, + .unknown, + UserAgent(deviceType: .mobile, + deviceModel: nil, + deviceOS: nil, + clientName: "Element", + clientVersion: "1.9.9;") + ] + + XCTAssertEqual(userAgents, expected) + } + +} diff --git a/changelog.d/pr-6766.change b/changelog.d/pr-6766.change new file mode 100644 index 000000000..3f5ff2693 --- /dev/null +++ b/changelog.d/pr-6766.change @@ -0,0 +1 @@ +UserSessions: Extended device information (PSG-772). From d59dd091958366160e39446c54165e7e0809a783 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Thu, 29 Sep 2022 12:23:08 +0100 Subject: [PATCH 087/104] Manual device verification with Crypto V2 --- .../User/UserVerificationCoordinator.swift | 1 + .../UserVerificationCoordinatorBridgePresenter.swift | 8 +++++++- .../Members/Detail/RoomMemberDetailsViewController.m | 10 +++++++++- .../ManageSession/ManageSessionViewController.m | 10 +++++++++- changelog.d/6781.change | 1 + 5 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 changelog.d/6781.change diff --git a/Riot/Modules/KeyVerification/User/UserVerificationCoordinator.swift b/Riot/Modules/KeyVerification/User/UserVerificationCoordinator.swift index b56748a40..45e8e378f 100644 --- a/Riot/Modules/KeyVerification/User/UserVerificationCoordinator.swift +++ b/Riot/Modules/KeyVerification/User/UserVerificationCoordinator.swift @@ -209,6 +209,7 @@ extension UserVerificationCoordinator: KeyVerificationManuallyVerifyCoordinatorD self.presenter.toPresentable().dismiss(animated: true) { self.remove(childCoordinator: coordinator) } + delegate?.userVerificationCoordinatorDidComplete(self) } func keyVerificationManuallyVerifyCoordinatorDidCancel(_ coordinator: KeyVerificationManuallyVerifyCoordinatorType) { diff --git a/Riot/Modules/KeyVerification/User/UserVerificationCoordinatorBridgePresenter.swift b/Riot/Modules/KeyVerification/User/UserVerificationCoordinatorBridgePresenter.swift index af19e72f3..ca718aa1e 100644 --- a/Riot/Modules/KeyVerification/User/UserVerificationCoordinatorBridgePresenter.swift +++ b/Riot/Modules/KeyVerification/User/UserVerificationCoordinatorBridgePresenter.swift @@ -77,8 +77,14 @@ final class UserVerificationCoordinatorBridgePresenter: NSObject { } else { userVerificationCoordinator = UserVerificationCoordinator(presenter: self.presenter, session: self.session, userId: self.userId, userDisplayName: self.userDisplayName) } - + userVerificationCoordinator.delegate = self userVerificationCoordinator.start() self.coordinator = userVerificationCoordinator } } + +extension UserVerificationCoordinatorBridgePresenter: UserVerificationCoordinatorDelegate { + func userVerificationCoordinatorDidComplete(_ coordinator: UserVerificationCoordinatorType) { + delegate?.userVerificationCoordinatorBridgePresenterDelegateDidComplete(self) + } +} diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m index 50f0871d5..7e1ba5129 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -32,7 +32,7 @@ #define TABLEVIEW_ROW_CELL_HEIGHT 46 #define TABLEVIEW_SECTION_HEADER_HEIGHT 28 -@interface RoomMemberDetailsViewController () +@interface RoomMemberDetailsViewController () { RoomMemberTitleView* memberTitleView; @@ -449,6 +449,7 @@ session:self.mxRoom.mxSession userId:self.mxRoomMember.userId userDisplayName:self.mxRoomMember.displayname]; + userVerificationCoordinatorBridgePresenter.delegate = self; [userVerificationCoordinatorBridgePresenter start]; self.userVerificationCoordinatorBridgePresenter = userVerificationCoordinatorBridgePresenter; } @@ -1345,4 +1346,11 @@ keyVerificationCoordinatorBridgePresenter = nil; } +#pragma mark - UserVerificationCoordinatorBridgePresenterDelegate + +- (void)userVerificationCoordinatorBridgePresenterDelegateDidComplete:(UserVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + [self refreshUserEncryptionTrustLevel]; +} + @end diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m index 991f4544a..bab229a54 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -45,7 +45,7 @@ enum { }; -@interface ManageSessionViewController () +@interface ManageSessionViewController () { // The device to display MXDevice *device; @@ -649,6 +649,7 @@ enum { userId:self.mainSession.myUser.userId userDisplayName:nil deviceId:device.deviceId]; + userVerificationCoordinatorBridgePresenter.delegate = self; [userVerificationCoordinatorBridgePresenter start]; self.userVerificationCoordinatorBridgePresenter = userVerificationCoordinatorBridgePresenter; } @@ -701,4 +702,11 @@ enum { self.reauthenticationCoordinatorBridgePresenter = reauthenticationPresenter; } +#pragma mark - UserVerificationCoordinatorBridgePresenterDelegate + +- (void)userVerificationCoordinatorBridgePresenterDelegateDidComplete:(UserVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + [self reloadDeviceWithCompletion:^{}]; +} + @end diff --git a/changelog.d/6781.change b/changelog.d/6781.change new file mode 100644 index 000000000..7ff78b730 --- /dev/null +++ b/changelog.d/6781.change @@ -0,0 +1 @@ +CryptoV2: Manual device verification From ff7f9bc7fd966bb743201de6458aa334a6ca8ef0 Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Thu, 29 Sep 2022 16:51:32 +0200 Subject: [PATCH 088/104] Unable to send message after filtering for room - Fixed --- .../AllChats/AllChatsViewController.swift | 48 +++++++++++++------ .../Home/AllChats/AllChatsViewController.xib | 22 ++++----- changelog.d/6755.bugfix | 1 + 3 files changed, 44 insertions(+), 27 deletions(-) create mode 100644 changelog.d/6755.bugfix diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index 291ea8322..30a9ee6cc 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -75,6 +75,23 @@ class AllChatsViewController: HomeViewController { private var currentAlert: UIAlertController? + @IBOutlet private var toolbar: UIToolbar! + private var isToolbarHidden: Bool = false { + didSet { + if isViewLoaded { + toolbar.transform = isToolbarHidden ? CGAffineTransform(translationX: 0, y: 2 * toolbarHeight) : .identity + self.view.layoutIfNeeded() + } + } + } + + private func setToolbarHidden(_ isHidden: Bool, animated: Bool) { + UIView.animate(withDuration: animated ? 0.3 : 0) { + self.isToolbarHidden = isHidden + } + + } + // MARK: - SplitViewMasterViewControllerProtocol // References on the currently selected room @@ -91,6 +108,8 @@ class AllChatsViewController: HomeViewController { // Tell whether the onboarding screen is preparing. private(set) var isOnboardingInProgress: Bool = false + + private var toolbarHeight: CGFloat = 0 // MARK: - Lifecycle @@ -107,6 +126,8 @@ class AllChatsViewController: HomeViewController { recentsTableView.register(RecentsInvitesTableViewCell.nib, forCellReuseIdentifier: RecentsInvitesTableViewCell.reuseIdentifier) recentsTableView.contentInsetAdjustmentBehavior = .automatic + toolbarHeight = toolbar.frame.height + updateUI() navigationItem.largeTitleDisplayMode = .automatic @@ -122,8 +143,7 @@ class AllChatsViewController: HomeViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - self.navigationController?.isToolbarHidden = false - self.navigationController?.toolbar.tintColor = ThemeService.shared().theme.colors.accent + self.toolbar.tintColor = ThemeService.shared().theme.colors.accent if self.navigationItem.searchController == nil { self.navigationItem.searchController = searchController } @@ -164,12 +184,6 @@ class AllChatsViewController: HomeViewController { } } - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - self.navigationController?.isToolbarHidden = true - } - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) @@ -372,8 +386,8 @@ class AllChatsViewController: HomeViewController { let scrollPosition = scrollPosition(of: scrollView) - if !self.recentsTableView.isDragging && scrollPosition == 0 && self.navigationController?.isToolbarHidden == true { - self.navigationController?.setToolbarHidden(false, animated: true) + if !self.recentsTableView.isDragging && scrollPosition == 0 && self.isToolbarHidden == true { + self.setToolbarHidden(false, animated: true) } guard self.recentsTableView.isDragging else { @@ -385,8 +399,8 @@ class AllChatsViewController: HomeViewController { } let isToolBarHidden: Bool = scrollPosition - initialScrollPosition > 0 - if isToolBarHidden != self.navigationController?.isToolbarHidden { - self.navigationController?.setToolbarHidden(isToolBarHidden, animated: true) + if isToolBarHidden != self.isToolbarHidden { + self.setToolbarHidden(isToolBarHidden, animated: true) } } @@ -494,13 +508,17 @@ class AllChatsViewController: HomeViewController { } private func updateToolbar(with menu: UIMenu) { - self.navigationController?.isToolbarHidden = false + guard isViewLoaded else { + return + } + + self.isToolbarHidden = false self.update(with: ThemeService.shared().theme) - self.setToolbarItems([ + self.toolbar.items = [ UIBarButtonItem(image: Asset.Images.allChatsSpacesIcon.image, style: .done, target: self, action: #selector(self.showSpaceSelectorAction(sender: ))), UIBarButtonItem.flexibleSpace(), UIBarButtonItem(image: Asset.Images.allChatsEditIcon.image, menu: menu) - ], animated: true) + ] } private func showCreateSpace(parentSpaceId: String?) { diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.xib b/Riot/Modules/Home/AllChats/AllChatsViewController.xib index 83b4849ab..59bdeffa1 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.xib +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.xib @@ -11,8 +11,7 @@ - - + @@ -25,25 +24,24 @@ - - - - + + - + - + + - - - - + + + + diff --git a/changelog.d/6755.bugfix b/changelog.d/6755.bugfix new file mode 100644 index 000000000..3e1d8ee1d --- /dev/null +++ b/changelog.d/6755.bugfix @@ -0,0 +1 @@ +App Layout: Unable to send message after filtering for room \ No newline at end of file From 5396e7351b1affecd4fe4b11c9b3dbb6d29cc233 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Fri, 30 Sep 2022 13:49:16 +0300 Subject: [PATCH 089/104] Improve user session overview tests * Add sessions overview UI tests * Expose static methods from the UserSession name and lastActivity formatters; cleaned up the UserSessionsOverview a bit * Add UserSessionsOverviewViewModel unit tests * Add UserSessionsOverviewService unit tests --- Riot/target.yml | 3 + .../UserSessionLastActivityFormatter.swift | 6 +- .../Common/UserSessionNameFormatter.swift | 4 +- .../Common/View/UserSessionCardView.swift | 2 + .../Common/View/UserSessionCardViewData.swift | 7 +- .../UserSessionsOverviewCoordinator.swift | 3 +- .../MockUserSessionsOverviewScreenState.swift | 32 ++- .../MatrixSDK/UserSessionsDataProvider.swift | 50 ++++ .../UserSessionsDataProviderProtocol.swift | 32 +++ .../UserSessionsOverviewService.swift | 28 +- .../MockUserSessionsOverviewService.swift | 161 +++++++---- .../Test/UI/UserSessionsOverviewUITests.swift | 35 ++- .../UserSessionsOverviewViewModelTests.swift | 72 ++++- .../UserSessionsOverviewModels.swift | 2 +- .../View/UserSessionListItemViewData.swift | 7 +- .../View/UserSessionsOverview.swift | 70 ++--- RiotSwiftUI/target.yml | 3 + RiotSwiftUI/targetUITests.yml | 3 + RiotSwiftUI/targetUnitTests.yml | 3 + .../UserSessionsOverviewServiceTests.swift | 267 ++++++++++++++++++ RiotTests/target.yml | 3 + 21 files changed, 650 insertions(+), 143 deletions(-) create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProvider.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProviderProtocol.swift create mode 100644 RiotTests/UserSessionsOverviewServiceTests.swift diff --git a/Riot/target.yml b/Riot/target.yml index 1d6b61fbb..c92a05d6f 100644 --- a/Riot/target.yml +++ b/Riot/target.yml @@ -24,6 +24,9 @@ schemes: disableMainThreadChecker: true targets: - RiotTests + gatherCoverageData: true + coverageTargets: + - Riot targets: Riot: diff --git a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionLastActivityFormatter.swift b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionLastActivityFormatter.swift index 3c63d13b0..0aa253ae7 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionLastActivityFormatter.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionLastActivityFormatter.swift @@ -17,7 +17,7 @@ import Foundation /// Enables to build last activity date string -class UserSessionLastActivityFormatter { +enum UserSessionLastActivityFormatter { private static var lastActivityDateFormatter: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.locale = Locale.current @@ -28,9 +28,9 @@ class UserSessionLastActivityFormatter { }() /// Session last activity string - func lastActivityDateString(from lastActivityTimestamp: TimeInterval) -> String { + static func lastActivityDateString(from lastActivityTimestamp: TimeInterval) -> String { let date = Date(timeIntervalSince1970: lastActivityTimestamp) - return UserSessionLastActivityFormatter.lastActivityDateFormatter.string(from: date) + return Self.lastActivityDateFormatter.string(from: date) } } diff --git a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionNameFormatter.swift b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionNameFormatter.swift index 04dbd79f6..492e27226 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionNameFormatter.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionNameFormatter.swift @@ -17,9 +17,9 @@ import Foundation /// Enables to build user session name -class UserSessionNameFormatter { +enum UserSessionNameFormatter { /// Session name with client name and session display name - func sessionName(deviceType: DeviceType, sessionDisplayName: String?) -> String { + static func sessionName(deviceType: DeviceType, sessionDisplayName: String?) -> String { let sessionName: String let clientName = deviceType.name diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift index 3736322ef..3da87b619 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift @@ -109,12 +109,14 @@ struct UserSessionCardView: View { .buttonStyle(PrimaryActionButtonStyle()) .padding(.top, 4) .padding(.bottom, 3) + .accessibilityIdentifier("userSessionCardVerifyButton") } if viewData.isCurrentSessionDisplayMode { Text(VectorL10n.userSessionViewDetails) .font(theme.fonts.body) .foregroundColor(theme.colors.accent) + .accessibilityIdentifier("userSessionCardViewDetails") } } .padding(24) diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift index 339de5806..35a6abb55 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift @@ -18,9 +18,6 @@ import Foundation /// View data for UserSessionCardView struct UserSessionCardViewData { - private static let userSessionNameFormatter = UserSessionNameFormatter() - private static let lastActivityDateFormatter = UserSessionLastActivityFormatter() - var id: String { sessionId } @@ -48,13 +45,13 @@ struct UserSessionCardViewData { lastSeenIP: String?, isCurrentSessionDisplayMode: Bool = false) { self.sessionId = sessionId - sessionName = Self.userSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName) + sessionName = UserSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName) self.isVerified = isVerified var lastActivityDateString: String? if let lastActivityTimestamp = lastActivityTimestamp { - lastActivityDateString = Self.lastActivityDateFormatter.lastActivityDateString(from: lastActivityTimestamp) + lastActivityDateString = UserSessionLastActivityFormatter.lastActivityDateString(from: lastActivityTimestamp) } self.lastActivityDateString = lastActivityDateString diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift index c6316569d..c32202059 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift @@ -37,7 +37,8 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { init(parameters: UserSessionsOverviewCoordinatorParameters) { self.parameters = parameters - service = UserSessionsOverviewService(mxSession: parameters.session) + let dataProvider = UserSessionsDataProvider(session: parameters.session) + service = UserSessionsOverviewService(dataProvider: dataProvider) viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service) hostingViewController = VectorHostingController(rootView: UserSessionsOverview(viewModel: viewModel.context)) indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: hostingViewController) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/MockUserSessionsOverviewScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/MockUserSessionsOverviewScreenState.swift index 01e53c636..626c8c07a 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/MockUserSessionsOverviewScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/MockUserSessionsOverviewScreenState.swift @@ -20,31 +20,39 @@ import SwiftUI /// Using an enum for the screen allows you define the different state cases with /// the relevant associated data for each case. enum MockUserSessionsOverviewScreenState: MockScreenState, CaseIterable { - case verifiedSession + case currentSessionUnverified + case currentSessionVerified + case onlyUnverifiedSessions + case onlyInactiveSessions + case noOtherSessions /// The associated screen var screenType: Any.Type { UserSessionsOverview.self } - /// A list of screen state definitions - static var allCases: [MockUserSessionsOverviewScreenState] { - // Each of the presence statuses - [.verifiedSession] - } - /// Generate the view struct for the screen state. var screenView: ([Any], AnyView) { - let service = MockUserSessionsOverviewService() + var service: UserSessionsOverviewServiceProtocol? switch self { - case .verifiedSession: - break + case .currentSessionUnverified: + service = MockUserSessionsOverviewService(mode: .currentSessionUnverified) + case .currentSessionVerified: + service = MockUserSessionsOverviewService(mode: .currentSessionVerified) + case .onlyUnverifiedSessions: + service = MockUserSessionsOverviewService(mode: .onlyUnverifiedSessions) + case .onlyInactiveSessions: + service = MockUserSessionsOverviewService(mode: .onlyInactiveSessions) + case .noOtherSessions: + service = MockUserSessionsOverviewService(mode: .noOtherSessions) + } + + guard let service = service else { + fatalError() } let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service) - // can simulate service and viewModel actions here if needs be. - return ( [service, viewModel], AnyView(UserSessionsOverview(viewModel: viewModel.context) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProvider.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProvider.swift new file mode 100644 index 000000000..5ddf57bde --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProvider.swift @@ -0,0 +1,50 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import MatrixSDK + +class UserSessionsDataProvider: UserSessionsDataProviderProtocol { + private let session: MXSession + + init(session: MXSession) { + self.session = session + } + + var myDeviceId: String { + session.myDeviceId + } + + var myUserId: String? { + session.myUserId + } + + var activeAccounts: [MXKAccount] { + MXKAccountManager.shared().activeAccounts + } + + func devices(completion: @escaping (MXResponse<[MXDevice]>) -> Void) { + session.matrixRestClient.devices(completion: completion) + } + + func device(withDeviceId deviceId: String, ofUser userId: String) -> MXDeviceInfo { + session.crypto.device(withDeviceId: deviceId, ofUser: userId) + } + + func accountData(for eventType: String) -> [AnyHashable : Any]? { + session.accountData.accountData(forEventType: eventType) + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProviderProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProviderProtocol.swift new file mode 100644 index 000000000..8537f93fa --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProviderProtocol.swift @@ -0,0 +1,32 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import MatrixSDK + +protocol UserSessionsDataProviderProtocol { + var myDeviceId: String { get } + + var myUserId: String? { get } + + var activeAccounts: [MXKAccount] { get } + + func devices(completion: @escaping (MXResponse<[MXDevice]>) -> Void) + + func device(withDeviceId deviceId: String, ofUser userId: String) -> MXDeviceInfo + + func accountData(for eventType: String) -> [AnyHashable: Any]? +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift index 874e5dfa1..a84cd7d44 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift @@ -21,12 +21,12 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { /// Delay after which session is considered inactive, 90 days private static let inactiveSessionDurationTreshold: TimeInterval = 90 * 86400 - private let mxSession: MXSession + private let dataProvider: UserSessionsDataProviderProtocol private(set) var overviewData: UserSessionsOverviewData - init(mxSession: MXSession) { - self.mxSession = mxSession + init(dataProvider: UserSessionsDataProviderProtocol) { + self.dataProvider = dataProvider overviewData = UserSessionsOverviewData(currentSession: nil, unverifiedSessions: [], @@ -39,7 +39,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { // MARK: - Public func updateOverviewData(completion: @escaping (Result) -> Void) { - mxSession.matrixRestClient.devices { response in + dataProvider.devices { response in switch response { case .success(let devices): self.overviewData = self.sessionsOverviewData(from: devices) @@ -61,16 +61,18 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { // MARK: - Private private func setupInitialOverviewData() { - let currentSessionInfo = currentSessionInfo() + guard let currentSessionInfo = getCurrentSessionInfo() else { + return + } overviewData = UserSessionsOverviewData(currentSession: currentSessionInfo, - unverifiedSessions: [], - inactiveSessions: [], + unverifiedSessions: currentSessionInfo.isVerified ? [] : [currentSessionInfo], + inactiveSessions: currentSessionInfo.isActive ? [] : [currentSessionInfo], otherSessions: []) } - private func currentSessionInfo() -> UserSessionInfo? { - guard let mainAccount = MXKAccountManager.shared().activeAccounts.first, + private func getCurrentSessionInfo() -> UserSessionInfo? { + guard let mainAccount = dataProvider.activeAccounts.first, let device = mainAccount.device else { return nil } @@ -80,7 +82,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { private func sessionsOverviewData(from devices: [MXDevice]) -> UserSessionsOverviewData { let allSessions = devices .sorted { $0.lastSeenTs > $1.lastSeenTs } - .map { sessionInfo(from: $0, isCurrentSession: $0.deviceId == mxSession.myDeviceId) } + .map { sessionInfo(from: $0, isCurrentSession: $0.deviceId == dataProvider.myDeviceId) } return UserSessionsOverviewData(currentSession: allSessions.filter(\.isCurrent).first, unverifiedSessions: allSessions.filter { !$0.isVerified }, @@ -92,7 +94,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { let isSessionVerified = deviceInfo(for: device.deviceId)?.trustLevel.isVerified ?? false let eventType = kMXAccountDataTypeClientInformation + "." + device.deviceId - let appData = mxSession.accountData.accountData(forEventType: eventType) + let appData = dataProvider.accountData(for: eventType) var userAgent: UserAgent? var isSessionActive = true @@ -114,11 +116,11 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol { } private func deviceInfo(for deviceId: String) -> MXDeviceInfo? { - guard let userId = mxSession.myUserId else { + guard let userId = dataProvider.myUserId else { return nil } - return mxSession.crypto.device(withDeviceId: deviceId, ofUser: userId) + return dataProvider.device(withDeviceId: deviceId, ofUser: userId) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift index f9724f25e..00d90238b 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift @@ -17,28 +17,70 @@ import Foundation class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { - let overviewData: UserSessionsOverviewData + enum Mode { + case currentSessionUnverified + case currentSessionVerified + case onlyUnverifiedSessions + case onlyInactiveSessions + case noOtherSessions + } + + private let mode: Mode + + var overviewData: UserSessionsOverviewData + + init(mode: Mode = .currentSessionUnverified) { + self.mode = mode + + overviewData = UserSessionsOverviewData(currentSession: nil, + unverifiedSessions: [], + inactiveSessions: [], + otherSessions: []) + } func updateOverviewData(completion: @escaping (Result) -> Void) { + let unverifiedSessions = buildSessions(verified: false, active: true) + let inactiveSessions = buildSessions(verified: true, active: false) + + switch mode { + case .noOtherSessions: + overviewData = UserSessionsOverviewData(currentSession: currentSession, + unverifiedSessions: [], + inactiveSessions: [], + otherSessions: []) + case .onlyUnverifiedSessions: + overviewData = UserSessionsOverviewData(currentSession: currentSession, + unverifiedSessions: unverifiedSessions + [currentSession], + inactiveSessions: [], + otherSessions: unverifiedSessions) + case .onlyInactiveSessions: + overviewData = UserSessionsOverviewData(currentSession: currentSession, + unverifiedSessions: [], + inactiveSessions: inactiveSessions, + otherSessions: inactiveSessions) + default: + let otherSessions = unverifiedSessions + inactiveSessions + buildSessions(verified: true, active: true) + + overviewData = UserSessionsOverviewData(currentSession: currentSession, + unverifiedSessions: unverifiedSessions, + inactiveSessions: inactiveSessions, + otherSessions: otherSessions) + } + completion(.success(overviewData)) } func sessionForIdentifier(_ sessionId: String) -> UserSessionInfo? { - nil + overviewData.otherSessions.first { $0.id == sessionId } } + + // MARK: - Private - init() { - overviewData = UserSessionsOverviewData(currentSession: Self.allSessions.filter(\.isCurrent).first, - unverifiedSessions: Self.allSessions.filter { !$0.isVerified }, - inactiveSessions: Self.allSessions.filter { !$0.isActive }, - otherSessions: Self.allSessions.filter { !$0.isCurrent }) - } - - static let allSessions = [ + private var currentSession: UserSessionInfo { UserSessionInfo(id: "alice", name: "iOS", deviceType: .mobile, - isVerified: false, + isVerified: mode == .currentSessionVerified, lastSeenIP: "10.0.0.10", lastSeenTimestamp: nil, applicationName: "Element iOS", @@ -49,51 +91,54 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { lastSeenIPLocation: nil, deviceName: "My iPhone", isActive: true, - isCurrent: true), - UserSessionInfo(id: "1", - name: "macOS", - deviceType: .desktop, - isVerified: true, - lastSeenIP: "1.0.0.1", - lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000, - applicationName: "Element MacOS", - applicationVersion: "1.0.0", - applicationURL: nil, - deviceModel: nil, - deviceOS: "macOS 12.5.1", - lastSeenIPLocation: nil, - deviceName: "My Mac", - isActive: false, - isCurrent: false), - UserSessionInfo(id: "2", - name: "Firefox on Windows", - deviceType: .web, - isVerified: true, - lastSeenIP: "2.0.0.2", - lastSeenTimestamp: Date().timeIntervalSince1970 - 100, - applicationName: "Element Web", - applicationVersion: "1.0.0", - applicationURL: nil, - deviceModel: nil, - deviceOS: "Windows 10", - lastSeenIPLocation: nil, - deviceName: "My Windows", - isActive: true, - isCurrent: false), - UserSessionInfo(id: "3", - name: "Android", - deviceType: .mobile, - isVerified: false, - lastSeenIP: "3.0.0.3", - lastSeenTimestamp: Date().timeIntervalSince1970 - 10, - applicationName: "Element Android", - applicationVersion: "1.0.0", - applicationURL: nil, - deviceModel: nil, - deviceOS: "Android 4.0", - lastSeenIPLocation: nil, - deviceName: "My Phone", - isActive: true, - isCurrent: false) - ] + isCurrent: true) + } + + private func buildSessions(verified: Bool, active: Bool) -> [UserSessionInfo] { + [UserSessionInfo(id: "1 verified: \(verified) active: \(active)", + name: "macOS verified: \(verified) active: \(active)", + deviceType: .desktop, + isVerified: verified, + lastSeenIP: "1.0.0.1", + lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000, + applicationName: "Element MacOS", + applicationVersion: "1.0.0", + applicationURL: nil, + deviceModel: nil, + deviceOS: "macOS 12.5.1", + lastSeenIPLocation: nil, + deviceName: "My Mac", + isActive: active, + isCurrent: false), + UserSessionInfo(id: "2 verified: \(verified) active: \(active)", + name: "Firefox on Windows verified: \(verified) active: \(active)", + deviceType: .web, + isVerified: verified, + lastSeenIP: "2.0.0.2", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100, + applicationName: "Element Web", + applicationVersion: "1.0.0", + applicationURL: nil, + deviceModel: nil, + deviceOS: "Windows 10", + lastSeenIPLocation: nil, + deviceName: "My Windows", + isActive: active, + isCurrent: false), + UserSessionInfo(id: "3 verified: \(verified) active: \(active)", + name: "Android verified: \(verified) active: \(active)", + deviceType: .mobile, + isVerified: verified, + lastSeenIP: "3.0.0.3", + lastSeenTimestamp: Date().timeIntervalSince1970 - 10, + applicationName: "Element Android", + applicationVersion: "1.0.0", + applicationURL: nil, + deviceModel: nil, + deviceOS: "Android 4.0", + lastSeenIPLocation: nil, + deviceName: "My Phone", + isActive: active, + isCurrent: false)] + } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/UI/UserSessionsOverviewUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/UI/UserSessionsOverviewUITests.swift index caede94d6..f05203cb7 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/UI/UserSessionsOverviewUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/UI/UserSessionsOverviewUITests.swift @@ -18,5 +18,38 @@ import RiotSwiftUI import XCTest class UserSessionsOverviewUITests: MockScreenTestCase { - // TODO: + func testCurrentSessionUnverified() { + app.goToScreenWithIdentifier(MockUserSessionsOverviewScreenState.currentSessionUnverified.title) + + XCTAssertTrue(app.buttons["userSessionCardVerifyButton"].exists) + XCTAssertTrue(app.staticTexts["userSessionCardViewDetails"].exists) + } + + func testCurrentSessionVerified() { + app.goToScreenWithIdentifier(MockUserSessionsOverviewScreenState.currentSessionVerified.title) + + XCTAssertFalse(app.buttons["userSessionCardVerifyButton"].exists) + XCTAssertTrue(app.staticTexts["userSessionCardViewDetails"].exists) + } + + func testOnlyUnverifiedSessions() { + app.goToScreenWithIdentifier(MockUserSessionsOverviewScreenState.onlyUnverifiedSessions.title) + + XCTAssertTrue(app.staticTexts["userSessionsOverviewSecurityRecommendationsSection"].exists) + XCTAssertTrue(app.staticTexts["userSessionsOverviewOtherSection"].exists) + } + + func testOnlyInactiveSessions() { + app.goToScreenWithIdentifier(MockUserSessionsOverviewScreenState.onlyInactiveSessions.title) + + XCTAssertTrue(app.staticTexts["userSessionsOverviewSecurityRecommendationsSection"].exists) + XCTAssertTrue(app.staticTexts["userSessionsOverviewOtherSection"].exists) + } + + func testNoOtherSessions() { + app.goToScreenWithIdentifier(MockUserSessionsOverviewScreenState.noOtherSessions.title) + + XCTAssertFalse(app.staticTexts["userSessionsOverviewSecurityRecommendationsSection"].exists) + XCTAssertFalse(app.staticTexts["userSessionsOverviewOtherSection"].exists) + } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionsOverviewViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionsOverviewViewModelTests.swift index 1d24d1586..21389eafd 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionsOverviewViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionsOverviewViewModelTests.swift @@ -20,13 +20,71 @@ import XCTest @testable import RiotSwiftUI class UserSessionsOverviewViewModelTests: XCTestCase { - var service: MockUserSessionsOverviewService! - var viewModel: UserSessionsOverviewViewModelProtocol! - var context: UserSessionsOverviewViewModelType.Context! + func testInitialStateEmpty() { + let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService()) + + XCTAssertNil(viewModel.state.currentSessionViewData) + XCTAssertTrue(viewModel.state.unverifiedSessionsViewData.isEmpty) + XCTAssertTrue(viewModel.state.inactiveSessionsViewData.isEmpty) + XCTAssertTrue(viewModel.state.otherSessionsViewData.isEmpty) + } - override func setUpWithError() throws { - service = MockUserSessionsOverviewService() - viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service) - context = viewModel.context + func testLoadOnDidAppear() { + let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService()) + viewModel.process(viewAction: .viewAppeared) + + XCTAssertNotNil(viewModel.state.currentSessionViewData) + XCTAssertFalse(viewModel.state.unverifiedSessionsViewData.isEmpty) + XCTAssertFalse(viewModel.state.inactiveSessionsViewData.isEmpty) + XCTAssertFalse(viewModel.state.otherSessionsViewData.isEmpty) + } + + func testSimpleActionProcessing() { + let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService()) + + var result: UserSessionsOverviewViewModelResult? + viewModel.completion = { action in + result = action + } + + viewModel.process(viewAction: .verifyCurrentSession) + XCTAssertEqual(result, .verifyCurrentSession) + + viewModel.process(viewAction: .viewAllUnverifiedSessions) + XCTAssertEqual(result, .showAllUnverifiedSessions) + + viewModel.process(viewAction: .viewAllInactiveSessions) + XCTAssertEqual(result, .showAllInactiveSessions) + + viewModel.process(viewAction: .viewAllOtherSessions) + XCTAssertEqual(result, .showAllOtherSessions) + } + + func testShowSessionDetails() { + let service = MockUserSessionsOverviewService() + service.updateOverviewData { _ in } + + let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service) + + var result: UserSessionsOverviewViewModelResult? + viewModel.completion = { action in + result = action + } + + guard let currentSession = service.overviewData.currentSession else { + XCTFail("The current session should be valid at this point") + return + } + + viewModel.process(viewAction: .viewCurrentSessionDetails) + XCTAssertEqual(result, .showCurrentSessionOverview(session: currentSession)) + + guard let randomSession = service.overviewData.otherSessions.randomElement() else { + XCTFail("There should be other sessions") + return + } + + viewModel.process(viewAction: .tapUserSession(randomSession.id)) + XCTAssertEqual(result, .showUserSessionOverview(session: randomSession)) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift index ce3e56f0a..2b3ae080a 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift @@ -24,7 +24,7 @@ enum UserSessionsOverviewCoordinatorResult { // MARK: View model -enum UserSessionsOverviewViewModelResult { +enum UserSessionsOverviewViewModelResult: Equatable { case showAllUnverifiedSessions case showAllInactiveSessions case verifyCurrentSession diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift index 56bccbbad..af13e572b 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewData.swift @@ -18,9 +18,6 @@ import Foundation /// View data for UserSessionListItem struct UserSessionListItemViewData: Identifiable { - private static let userSessionNameFormatter = UserSessionNameFormatter() - private static let lastActivityDateFormatter = UserSessionLastActivityFormatter() - var id: String { sessionId } @@ -39,7 +36,7 @@ struct UserSessionListItemViewData: Identifiable { isVerified: Bool, lastActivityDate: TimeInterval?) { self.sessionId = sessionId - sessionName = Self.userSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName) + sessionName = UserSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName) sessionDetails = Self.buildSessionDetails(isVerified: isVerified, lastActivityDate: lastActivityDate) deviceAvatarViewData = DeviceAvatarViewData(deviceType: deviceType, isVerified: isVerified) } @@ -54,7 +51,7 @@ struct UserSessionListItemViewData: Identifiable { var lastActivityDateString: String? if let lastActivityDate = lastActivityDate { - lastActivityDateString = Self.lastActivityDateFormatter.lastActivityDateString(from: lastActivityDate) + lastActivityDateString = UserSessionLastActivityFormatter.lastActivityDateString(from: lastActivityDate) } if let lastActivityDateString = lastActivityDateString, lastActivityDateString.isEmpty == false { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift index 925ab82b7..6823c9253 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift @@ -23,11 +23,13 @@ struct UserSessionsOverview: View { var body: some View { ScrollView { - securityRecommendationsSection + if hasSecurityRecommendations { + securityRecommendationsSection + } currentSessionsSection - if viewModel.viewState.otherSessionsViewData.isEmpty == false { + if !viewModel.viewState.otherSessionsViewData.isEmpty { otherSessionsSection } } @@ -40,41 +42,39 @@ struct UserSessionsOverview: View { } } - @ViewBuilder private var securityRecommendationsSection: some View { - if hasSecurityRecommendations { - SwiftUI.Section { - if !viewModel.viewState.unverifiedSessionsViewData.isEmpty { - SecurityRecommendationCard(style: .unverified, - sessionCount: viewModel.viewState.unverifiedSessionsViewData.count) { - viewModel.send(viewAction: .viewAllUnverifiedSessions) - } + SwiftUI.Section { + if !viewModel.viewState.unverifiedSessionsViewData.isEmpty { + SecurityRecommendationCard(style: .unverified, + sessionCount: viewModel.viewState.unverifiedSessionsViewData.count) { + viewModel.send(viewAction: .viewAllUnverifiedSessions) } - - if !viewModel.viewState.inactiveSessionsViewData.isEmpty { - SecurityRecommendationCard(style: .inactive, - sessionCount: viewModel.viewState.inactiveSessionsViewData.count) { - viewModel.send(viewAction: .viewAllInactiveSessions) - } - } - } header: { - VStack(alignment: .leading) { - Text(VectorL10n.userSessionsOverviewSecurityRecommendationsSectionTitle) - .textCase(.uppercase) - .font(theme.fonts.footnote) - .foregroundColor(theme.colors.secondaryContent) - .padding(.bottom, 8.0) - - Text(VectorL10n.userSessionsOverviewSecurityRecommendationsSectionInfo) - .font(theme.fonts.footnote) - .foregroundColor(theme.colors.secondaryContent) - .padding(.bottom, 12.0) - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.top, 24) } - .padding(.horizontal, 16) + + if !viewModel.viewState.inactiveSessionsViewData.isEmpty { + SecurityRecommendationCard(style: .inactive, + sessionCount: viewModel.viewState.inactiveSessionsViewData.count) { + viewModel.send(viewAction: .viewAllInactiveSessions) + } + } + } header: { + VStack(alignment: .leading) { + Text(VectorL10n.userSessionsOverviewSecurityRecommendationsSectionTitle) + .textCase(.uppercase) + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.secondaryContent) + .padding(.bottom, 8.0) + + Text(VectorL10n.userSessionsOverviewSecurityRecommendationsSectionInfo) + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.secondaryContent) + .padding(.bottom, 12.0) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.top, 24) } + .padding(.horizontal, 16) + .accessibilityIdentifier("userSessionsOverviewSecurityRecommendationsSection") } var hasSecurityRecommendations: Bool { @@ -102,10 +102,9 @@ struct UserSessionsOverview: View { .padding(.horizontal, 16) } } - + private var otherSessionsSection: some View { SwiftUI.Section { - // Device list LazyVStack(spacing: 0) { ForEach(viewModel.viewState.otherSessionsViewData) { viewData in UserSessionListItem(viewData: viewData, onBackgroundTap: { sessionId in @@ -131,6 +130,7 @@ struct UserSessionsOverview: View { .padding(.horizontal, 16.0) .padding(.top, 24.0) } + .accessibilityIdentifier("userSessionsOverviewOtherSection") } } diff --git a/RiotSwiftUI/target.yml b/RiotSwiftUI/target.yml index ed0ebee39..3e3635e47 100644 --- a/RiotSwiftUI/target.yml +++ b/RiotSwiftUI/target.yml @@ -24,6 +24,9 @@ schemes: disableMainThreadChecker: true targets: - RiotSwiftUnitTests + gatherCoverageData: true + coverageTargets: + - RiotSwiftUI targets: RiotSwiftUI: diff --git a/RiotSwiftUI/targetUITests.yml b/RiotSwiftUI/targetUITests.yml index 62b78e18a..f43d9d4ad 100644 --- a/RiotSwiftUI/targetUITests.yml +++ b/RiotSwiftUI/targetUITests.yml @@ -24,6 +24,9 @@ schemes: disableMainThreadChecker: true targets: - RiotSwiftUITests + gatherCoverageData: true + coverageTargets: + - RiotSwiftUI targets: RiotSwiftUITests: diff --git a/RiotSwiftUI/targetUnitTests.yml b/RiotSwiftUI/targetUnitTests.yml index a0d596d55..6362bf447 100644 --- a/RiotSwiftUI/targetUnitTests.yml +++ b/RiotSwiftUI/targetUnitTests.yml @@ -24,6 +24,9 @@ schemes: disableMainThreadChecker: true targets: - RiotSwiftUnitTests + gatherCoverageData: true + coverageTargets: + - RiotSwiftUI targets: RiotSwiftUnitTests: diff --git a/RiotTests/UserSessionsOverviewServiceTests.swift b/RiotTests/UserSessionsOverviewServiceTests.swift new file mode 100644 index 000000000..1c7e50d5d --- /dev/null +++ b/RiotTests/UserSessionsOverviewServiceTests.swift @@ -0,0 +1,267 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +@testable import Element +@testable import MatrixSDK + +private let currentDeviceId = "deviceId" +private let unverifiedDeviceId = "unverifiedDeviceId" +private let currentUserId = "userId" + +class UserSessionsOverviewServiceTests: XCTestCase { + func testInitialSessionUnverified() { + let dataProvider = MockUserSessionsDataProvider(mode: .currentSessionUnverified) + let service = UserSessionsOverviewService(dataProvider: dataProvider) + + XCTAssertNotNil(service.overviewData.currentSession) + XCTAssertFalse(service.overviewData.currentSession?.isVerified ?? false) + XCTAssertTrue(service.overviewData.currentSession?.isActive ?? false) + XCTAssertFalse(service.overviewData.unverifiedSessions.isEmpty) + XCTAssertTrue(service.overviewData.inactiveSessions.isEmpty) + + XCTAssertEqual(service.sessionForIdentifier(currentDeviceId), service.overviewData.currentSession) + } + + func testInitialSessionVerified() { + let dataProvider = MockUserSessionsDataProvider(mode: .currentSessionVerified) + let service = UserSessionsOverviewService(dataProvider: dataProvider) + + XCTAssertNotNil(service.overviewData.currentSession) + XCTAssertTrue(service.overviewData.currentSession?.isVerified ?? false) + XCTAssertTrue(service.overviewData.currentSession?.isActive ?? false) + XCTAssertTrue(service.overviewData.unverifiedSessions.isEmpty) + XCTAssertTrue(service.overviewData.inactiveSessions.isEmpty) + } + + func testWithAllSessionsVerified() { + let service = setupServiceWithMode(.allOtherSessionsValid) + + XCTAssertNotNil(service.overviewData.currentSession) + XCTAssertTrue(service.overviewData.currentSession?.isVerified ?? false) + XCTAssertTrue(service.overviewData.currentSession?.isActive ?? false) + + XCTAssertTrue(service.overviewData.unverifiedSessions.isEmpty) + XCTAssertTrue(service.overviewData.inactiveSessions.isEmpty) + XCTAssertFalse(service.overviewData.otherSessions.isEmpty) + } + + func testWithSomeUnverifiedSessions() { + let service = setupServiceWithMode(.someUnverifiedSessions) + + XCTAssertNotNil(service.overviewData.currentSession) + XCTAssertTrue(service.overviewData.currentSession?.isVerified ?? false) + XCTAssertTrue(service.overviewData.currentSession?.isActive ?? false) + + XCTAssertFalse(service.overviewData.unverifiedSessions.isEmpty) + XCTAssertTrue(service.overviewData.inactiveSessions.isEmpty) + XCTAssertFalse(service.overviewData.otherSessions.isEmpty) + } + + func testWithSomeInactiveSessions() { + let service = setupServiceWithMode(.someInactiveSessions) + + XCTAssertNotNil(service.overviewData.currentSession) + XCTAssertTrue(service.overviewData.currentSession?.isVerified ?? false) + XCTAssertTrue(service.overviewData.currentSession?.isActive ?? false) + + XCTAssertTrue(service.overviewData.unverifiedSessions.isEmpty) + XCTAssertFalse(service.overviewData.inactiveSessions.isEmpty) + XCTAssertFalse(service.overviewData.otherSessions.isEmpty) + } + + func testWithSomeUnverifiedAndInactiveSessions() { + let service = setupServiceWithMode(.someUnverifiedAndInactiveSessions) + + XCTAssertNotNil(service.overviewData.currentSession) + XCTAssertTrue(service.overviewData.currentSession?.isVerified ?? false) + XCTAssertTrue(service.overviewData.currentSession?.isActive ?? false) + + XCTAssertFalse(service.overviewData.unverifiedSessions.isEmpty) + XCTAssertFalse(service.overviewData.inactiveSessions.isEmpty) + XCTAssertFalse(service.overviewData.otherSessions.isEmpty) + } + + // MARK: - Private + + private func setupServiceWithMode(_ mode: MockUserSessionsDataProvider.Mode) -> UserSessionsOverviewServiceProtocol { + let dataProvider = MockUserSessionsDataProvider(mode: mode) + let service = UserSessionsOverviewService(dataProvider: dataProvider) + + let expectation = expectation(description: "Wait for service update") + service.updateOverviewData { _ in + expectation.fulfill() + } + + waitForExpectations(timeout: 1.0) + + return service + } +} + +private class MockUserSessionsDataProvider: UserSessionsDataProviderProtocol { + enum Mode { + case currentSessionUnverified + case currentSessionVerified + case allOtherSessionsValid + case someUnverifiedSessions + case someInactiveSessions + case someUnverifiedAndInactiveSessions + } + + private let mode: Mode + + var myDeviceId = currentDeviceId + + var myUserId: String? = currentUserId + + var activeAccounts: [MXKAccount] { + [MockAccount()] + } + + init(mode: Mode) { + self.mode = mode + } + + func devices(completion: @escaping (MXResponse<[MXDevice]>) -> Void) { + DispatchQueue.main.async { + switch self.mode { + case .currentSessionUnverified: + return + case .currentSessionVerified: + return + case .allOtherSessionsValid: + completion(.success(self.verifiedSessions)) + case .someUnverifiedSessions: + completion(.success(self.verifiedSessions + self.unverifiedSessions)) + case .someInactiveSessions: + completion(.success(self.verifiedSessions + self.inactiveSessions)) + case .someUnverifiedAndInactiveSessions: + completion(.success(self.verifiedSessions + self.unverifiedSessions + self.inactiveSessions)) + } + } + } + + func device(withDeviceId deviceId: String, ofUser userId: String) -> MXDeviceInfo { + guard deviceId == currentDeviceId else { + return MockDeviceInfo(verified: deviceId != unverifiedDeviceId) + } + + switch mode { + case .currentSessionUnverified: + return MockDeviceInfo(verified: false) + default: + return MockDeviceInfo(verified: true) + } + } + + func accountData(for eventType: String) -> [AnyHashable : Any]? { + [:] + } + + // MARK: - Private + + var verifiedSessions: [MXDevice] { + [MockDevice(identifier: currentDeviceId, sessionActive: true), + MockDevice(identifier: UUID().uuidString, sessionActive: true)] + } + + var unverifiedSessions: [MXDevice] { + [MockDevice(identifier: unverifiedDeviceId, sessionActive: true)] + } + + var inactiveSessions: [MXDevice] { + [MockDevice(identifier: UUID().uuidString, sessionActive: false)] + } +} + +private class MockAccount: MXKAccount { + override var device: MXDevice? { + MockDevice(identifier: currentDeviceId, sessionActive: true) + } +} + +private class MockDevice: MXDevice { + private let identifier: String + private let sessionActive: Bool + + init(identifier: String, sessionActive: Bool) { + self.identifier = identifier + self.sessionActive = sessionActive + super.init() + } + + required init?(coder: NSCoder) { + fatalError() + } + + override var deviceId: String { + get { + identifier + } + set { + + } + } + + override var lastSeenTs: UInt64 { + get { + if sessionActive { + return UInt64(Date().timeIntervalSince1970 * 1000) + } else { + let ninetyDays: Double = 90 * 86400 + return UInt64((Date().timeIntervalSince1970 - ninetyDays) * 1000) + } + } + set { + + } + } +} + +private class MockDeviceInfo: MXDeviceInfo { + private let verified: Bool + + init(verified: Bool) { + self.verified = verified + super.init() + } + + required init?(coder: NSCoder) { + fatalError() + } + + override var trustLevel: MXDeviceTrustLevel! { + MockDeviceTrustLevel(verified: verified) + } +} + +private class MockDeviceTrustLevel: MXDeviceTrustLevel { + private let verified: Bool + + init(verified: Bool) { + self.verified = verified + super.init() + } + + required init?(coder: NSCoder) { + fatalError() + } + + override var isVerified: Bool { + verified + } +} diff --git a/RiotTests/target.yml b/RiotTests/target.yml index cde05e9ef..54c93b44a 100644 --- a/RiotTests/target.yml +++ b/RiotTests/target.yml @@ -24,6 +24,9 @@ schemes: disableMainThreadChecker: true targets: - RiotTests + gatherCoverageData: true + coverageTargets: + - Riot targets: RiotTests: From 15191dd9ff66d1e7978e788870324268fdf21023 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Fri, 30 Sep 2022 15:45:20 +0300 Subject: [PATCH 090/104] New session manager labs flag (PSG-792) (#6780) * Add new session manager labs flag * Add labs flag for client information feature * Add changelog --- Config/BuildSettings.swift | 4 -- Config/CommonConfiguration.swift | 2 + Riot/Assets/en.lproj/Vector.strings | 2 + Riot/Generated/Strings.swift | 8 ++++ Riot/Managers/Settings/RiotSettings.swift | 10 ++++- .../Modules/Settings/SettingsViewController.m | 45 ++++++++++++++++++- changelog.d/pr-6780.change | 1 + 7 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 changelog.d/pr-6780.change diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index 571809009..39de0496a 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -420,8 +420,4 @@ final class BuildSettings: NSObject { // MARK: - New App Layout static let newAppLayoutEnabled = true - - // MARK: - Device manager - - static let deviceManagerEnabled = false } diff --git a/Config/CommonConfiguration.swift b/Config/CommonConfiguration.swift index 6f9a7f555..d304f7fbe 100644 --- a/Config/CommonConfiguration.swift +++ b/Config/CommonConfiguration.swift @@ -89,6 +89,8 @@ class CommonConfiguration: NSObject, Configurable { sdkOptions.authEnableRefreshTokens = BuildSettings.authEnableRefreshTokens // Configure key provider delegate MXKeyProvider.sharedInstance().delegate = EncryptionKeyManager.shared + + sdkOptions.enableNewClientInformationFeature = RiotSettings.shared.enableClientInformationFeature } private func makeASCIIUserAgent() -> String? { diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 380ecd5c7..6449e8fbd 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -762,6 +762,8 @@ Tap the + to start adding people."; "settings_labs_enable_auto_report_decryption_errors" = "Auto Report Decryption Errors"; "settings_labs_use_only_latest_user_avatar_and_name" = "Show latest avatar and name for users in message history"; "settings_labs_enable_live_location_sharing" = "Live location sharing - share current location (active development, and temporarily, locations persist in room history)"; +"settings_labs_enable_new_session_manager" = "New session manager"; +"settings_labs_enable_new_client_info_feature" = "Record the client name, version, and url to recognise sessions more easily in session manager"; "settings_labs_enable_new_app_layout" = "New Application Layout"; "settings_version" = "Version %@"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 854994c3d..65bcd2560 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -7379,6 +7379,14 @@ public class VectorL10n: NSObject { public static var settingsLabsEnableNewAppLayout: String { return VectorL10n.tr("Vector", "settings_labs_enable_new_app_layout") } + /// Record the client name, version, and url to recognise sessions more easily in session manager + public static var settingsLabsEnableNewClientInfoFeature: String { + return VectorL10n.tr("Vector", "settings_labs_enable_new_client_info_feature") + } + /// New session manager + public static var settingsLabsEnableNewSessionManager: String { + return VectorL10n.tr("Vector", "settings_labs_enable_new_session_manager") + } /// Ring for group calls public static var settingsLabsEnableRingingForGroupCalls: String { return VectorL10n.tr("Vector", "settings_labs_enable_ringing_for_group_calls") diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index 7d0adfbe2..523ca86a4 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -163,7 +163,15 @@ final class RiotSettings: NSObject { NotificationCenter.default.post(name: RiotSettings.didUpdateLiveLocationSharingActivation, object: self) } } - + + /// Flag indicating if the new session manager is enabled + @UserDefault(key: "enableNewSessionManager", defaultValue: false, storage: defaults) + var enableNewSessionManager + + /// Flag indicating if the new client information feature is enabled + @UserDefault(key: "enableClientInformationFeature", defaultValue: false, storage: defaults) + var enableClientInformationFeature + // MARK: Calls /// Indicate if `allowStunServerFallback` settings has been set once. diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 9b69c0c19..1e1ec4d9f 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -172,7 +172,9 @@ typedef NS_ENUM(NSUInteger, LABS_ENABLE) LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX = 0, LABS_ENABLE_THREADS_INDEX, LABS_ENABLE_AUTO_REPORT_DECRYPTION_ERRORS, - LABS_ENABLE_LIVE_LOCATION_SHARING + LABS_ENABLE_LIVE_LOCATION_SHARING, + LABS_ENABLE_NEW_SESSION_MANAGER, + LABS_ENABLE_NEW_CLIENT_INFO_FEATURE }; typedef NS_ENUM(NSUInteger, SECURITY) @@ -403,7 +405,7 @@ ChangePasswordCoordinatorBridgePresenterDelegate> Section *sectionSecurity = [Section sectionWithTag:SECTION_TAG_SECURITY]; [sectionSecurity addRowWithTag:SECURITY_BUTTON_INDEX]; - if (BuildSettings.deviceManagerEnabled) + if (RiotSettings.shared.enableNewSessionManager) { // NOTE: Add device manager entry point in the security section atm for debug purpose [sectionSecurity addRowWithTag:DEVICE_MANAGER_INDEX]; @@ -595,6 +597,8 @@ ChangePasswordCoordinatorBridgePresenterDelegate> { [sectionLabs addRowWithTag:LABS_ENABLE_LIVE_LOCATION_SHARING]; } + [sectionLabs addRowWithTag:LABS_ENABLE_NEW_SESSION_MANAGER]; + [sectionLabs addRowWithTag:LABS_ENABLE_NEW_CLIENT_INFO_FEATURE]; sectionLabs.headerTitle = [VectorL10n settingsLabs]; if (sectionLabs.hasAnyRows) { @@ -2532,6 +2536,30 @@ ChangePasswordCoordinatorBridgePresenterDelegate> { cell = [self buildLiveLocationSharingCellForTableView:tableView atIndexPath:indexPath]; } + else if (row == LABS_ENABLE_NEW_SESSION_MANAGER) + { + MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsLabsEnableNewSessionManager]; + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableNewSessionManager; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableNewSessionManager:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; + } + else if (row == LABS_ENABLE_NEW_CLIENT_INFO_FEATURE) + { + MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsLabsEnableNewClientInfoFeature]; + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableClientInformationFeature; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableNewClientInfoFeature:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; + } } else if (section == SECTION_TAG_SECURITY) { @@ -3277,6 +3305,19 @@ ChangePasswordCoordinatorBridgePresenterDelegate> [[AppDelegate theDelegate] restoreEmptyDetailsViewController]; } +- (void)toggleEnableNewSessionManager:(UISwitch *)sender +{ + RiotSettings.shared.enableNewSessionManager = sender.isOn; + [self updateSections]; +} + +- (void)toggleEnableNewClientInfoFeature:(UISwitch *)sender +{ + BOOL isEnabled = sender.isOn; + RiotSettings.shared.enableClientInformationFeature = isEnabled; + MXSDKOptions.sharedInstance.enableNewClientInformationFeature = isEnabled; +} + - (void)togglePinRoomsWithMissedNotif:(UISwitch *)sender { RiotSettings.shared.pinRoomsWithMissedNotificationsOnHome = sender.isOn; diff --git a/changelog.d/pr-6780.change b/changelog.d/pr-6780.change new file mode 100644 index 000000000..a50689634 --- /dev/null +++ b/changelog.d/pr-6780.change @@ -0,0 +1 @@ +Settings: Add labs flags for new session manager (PSG-792, PSG-799). From f1dd0bee695c35960414a96372b4c6475684ce33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Fri, 30 Sep 2022 06:46:39 +0000 Subject: [PATCH 091/104] Translated using Weblate (Estonian) Currently translated at 100.0% (2198 of 2198 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ --- Riot/Assets/et.lproj/Vector.strings | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Riot/Assets/et.lproj/Vector.strings b/Riot/Assets/et.lproj/Vector.strings index 579b52229..d84210c12 100644 --- a/Riot/Assets/et.lproj/Vector.strings +++ b/Riot/Assets/et.lproj/Vector.strings @@ -2430,10 +2430,32 @@ "all_chats_empty_unreads_placeholder_message" = "Kui sul on lugemata sõnumeid, siis nad on siit leitavad."; "all_chats_empty_list_placeholder_title" = "Ei tea... kõik vist on nüüd tehtud."; "all_chats_empty_view_information" = "Paljude võimalustega turvaline suhtlusrakendus sõprade, kogukondade ja tiimide jaoks. Alustamiseks loo mõni uus vestlus või liitu olemasoleva jututoaga."; -"all_chats_empty_space_information" = "Kogukonnad on võimalus jututubade ja inimeste ühendamiseks. Kasutades all vasakul olevat nuppu lisa mõni olemasolev jututuba või loo uus."; +"all_chats_empty_space_information" = "Kogukonnad on võimalus jututubade ja inimeste ühendamiseks. Kasutades all paremal olevat nuppu lisa mõni olemasolev jututuba või loo uus."; "all_chats_empty_view_title" = "%@\ntundub olema tühjavõitu."; "spaces_explore_rooms_format" = "Uuri kogukonda: %@"; // User sessions management "user_sessions_settings" = "Halda sessioone"; "invite_to" = "Saada kutse siia: %@"; +"device_name_unknown" = "Tundmatu klientrakendus"; +"device_name_mobile" = "%@ Mobile"; +"device_name_web" = "%@ Web"; +"device_name_desktop" = "%@ Desktop"; +"user_session_item_details" = "%@ · Viimati kasutusel %@"; + +// First item is client name and second item is session display name +"user_session_name" = "%@: %@"; +"user_session_unverified_additional_info" = "Turvalise sõnumivahetuse nimel palun verifitseeri oma praegune sessioon."; +"user_session_verified_additional_info" = "Sinu praegune sessioon on valmis turvaliseks sõnumivahetuseks."; +"user_session_learn_more" = "Loe lisateavet"; +"user_session_view_details" = "Vaata lisateavet"; +"user_session_verify_action" = "Verifitseeri sessioon"; +"user_session_unverified_short" = "Verifitseerimata"; +"user_session_verified_short" = "Verifitseeritud"; +"user_session_unverified" = "Verifitseerimata sessioon"; +"user_session_verified" = "Verifitseeritud sessioon"; +"user_sessions_overview_current_session_section_title" = "PRAEGUNE SESSIOON"; +"user_sessions_overview_other_sessions_section_info" = "Parima turvalisuse nimel verifitseeri kõik oma sessioonid ning logi välja neist, mida sa enam ei kasuta."; +"user_sessions_overview_other_sessions_section_title" = "MUUD SESSIOONID"; +"settings_labs_enable_new_app_layout" = "Rakenduse uus paigutus"; +"room_first_message_placeholder" = "Saada oma esimene sõnum…"; From a01c0f77eae6c63a9999b22833d0e814f6416375 Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Mon, 3 Oct 2022 13:42:30 +0200 Subject: [PATCH 092/104] Added support for MSC3881 --- Riot/Assets/en.lproj/Vector.strings | 2 + Riot/Generated/Strings.swift | 8 ++ Riot/Managers/Call/CallPresenter.swift | 14 +-- .../Common/View/UserSessionCardView.swift | 4 +- .../Common/View/UserSessionCardViewData.swift | 16 +-- .../UserSessionsFlowCoordinator.swift | 8 +- .../UserSessionOverviewCoordinator.swift | 10 +- .../MockUserSessionOverviewScreenState.swift | 49 +++++++- .../UserSessionOverviewService.swift | 106 ++++++++++++++++++ .../Mock/MockUserSessionOverviewService.swift | 38 +++++++ .../UserSessionOverviewServiceProtocol.swift | 25 +++++ .../Test/UI/UserSessionOverviewUITests.swift | 27 +++++ .../UserSessionOverviewViewModelTests.swift | 49 +++++++- .../UserSessionOverviewModels.swift | 8 +- .../UserSessionOverviewViewModel.swift | 42 ++++++- .../View/UserSessionOverview.swift | 8 ++ .../UserSessionOverviewDisclosureCell.swift | 2 +- .../View/UserSessionOverviewToggleCell.swift | 86 ++++++++++++++ .../UserSessionsOverviewCoordinator.swift | 16 +-- .../MatrixSDK/UserSessionsDataProvider.swift | 2 +- .../UserSessionsDataProviderProtocol.swift | 2 +- .../UserSessionsOverviewModels.swift | 2 +- .../UserSessionsOverviewViewModel.swift | 2 +- changelog.d/6787.change | 1 + 24 files changed, 474 insertions(+), 53 deletions(-) create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Service/MatrixSDK/UserSessionOverviewService.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Service/Mock/MockUserSessionOverviewService.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Service/UserSessionOverviewServiceProtocol.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewToggleCell.swift create mode 100644 changelog.d/6787.change diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 380ecd5c7..3c075e84c 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2385,6 +2385,8 @@ To enable access, tap Settings> Location and select Always"; "user_session_verified_additional_info" = "Your current session is ready for secure messaging."; "user_session_unverified_additional_info" = "Verify your current session for enhanced secure messaging."; +"user_session_push_notifications" = "Push notifications"; +"user_session_push_notifications_message" = "When turned on, this session will receive push notifications."; // First item is client name and second item is session display name "user_session_name" = "%@: %@"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 854994c3d..1e635304f 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -8551,6 +8551,14 @@ public class VectorL10n: NSObject { public static var userSessionOverviewSessionTitle: String { return VectorL10n.tr("Vector", "user_session_overview_session_title") } + /// Push notifications + public static var userSessionPushNotifications: String { + return VectorL10n.tr("Vector", "user_session_push_notifications") + } + /// When turned on, this session will receive push notifications. + public static var userSessionPushNotificationsMessage: String { + return VectorL10n.tr("Vector", "user_session_push_notifications_message") + } /// Unverified session public static var userSessionUnverified: String { return VectorL10n.tr("Vector", "user_session_unverified") diff --git a/Riot/Managers/Call/CallPresenter.swift b/Riot/Managers/Call/CallPresenter.swift index b0c7c8ca3..683a791dd 100644 --- a/Riot/Managers/Call/CallPresenter.swift +++ b/Riot/Managers/Call/CallPresenter.swift @@ -455,11 +455,11 @@ class CallPresenter: NSObject { #if canImport(JitsiMeetSDK) JMCallKitProxy.addListener(self) - guard let session = sessions.first else { + guard let sessionInfo = sessions.first else { return } - widgetEventsListener = session.listenToEvents([ + widgetEventsListener = sessionInfo.listenToEvents([ MXEventType(identifier: kWidgetMatrixEventTypeString), MXEventType(identifier: kWidgetModularEventTypeString) ]) { (event, direction, _) in @@ -468,7 +468,7 @@ class CallPresenter: NSObject { return } - self.processWidgetEvent(event, inSession: session) + self.processWidgetEvent(event, inSession: sessionInfo) } #endif } @@ -496,12 +496,12 @@ class CallPresenter: NSObject { #if canImport(JitsiMeetSDK) JMCallKitProxy.removeListener(self) - guard let session = sessions.first else { + guard let sessionInfo = sessions.first else { return } if let widgetEventsListener = widgetEventsListener { - session.removeListener(widgetEventsListener) + sessionInfo.removeListener(widgetEventsListener) } widgetEventsListener = nil #endif @@ -872,11 +872,11 @@ extension CallPresenter: JMCallKitListener { } - func providerDidActivateAudioSession(session: AVAudioSession) { + func providerDidActivateAudioSession(sessionInfo: AVAudioSession) { } - func providerDidDeactivateAudioSession(session: AVAudioSession) { + func providerDidDeactivateAudioSession(sessionInfo: AVAudioSession) { } diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift index 3da87b619..212434309 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift @@ -138,7 +138,7 @@ struct UserSessionCardViewPreview: View { let viewData: UserSessionCardViewData init(isCurrent: Bool = false) { - let session = UserSessionInfo(id: "alice", + let sessionInfo = UserSessionInfo(id: "alice", name: "iOS", deviceType: .mobile, isVerified: false, @@ -153,7 +153,7 @@ struct UserSessionCardViewPreview: View { deviceName: "My iPhone", isActive: true, isCurrent: isCurrent) - viewData = UserSessionCardViewData(session: session) + viewData = UserSessionCardViewData(sessionInfo: sessionInfo) } var body: some View { diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift index 35a6abb55..fff1c6564 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardViewData.swift @@ -63,13 +63,13 @@ struct UserSessionCardViewData { } extension UserSessionCardViewData { - init(session: UserSessionInfo) { - self.init(sessionId: session.id, - sessionDisplayName: session.name, - deviceType: session.deviceType, - isVerified: session.isVerified, - lastActivityTimestamp: session.lastSeenTimestamp, - lastSeenIP: session.lastSeenIP, - isCurrentSessionDisplayMode: session.isCurrent) + init(sessionInfo: UserSessionInfo) { + self.init(sessionId: sessionInfo.id, + sessionDisplayName: sessionInfo.name, + deviceType: sessionInfo.deviceType, + isVerified: sessionInfo.isVerified, + lastActivityTimestamp: sessionInfo.lastSeenTimestamp, + lastSeenIP: sessionInfo.lastSeenIP, + isCurrentSessionDisplayMode: sessionInfo.isCurrent) } } diff --git a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift index 288ce2bf4..64cf32f40 100644 --- a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift @@ -53,8 +53,8 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { coordinator.completion = { [weak self] result in guard let self = self else { return } switch result { - case let .openSessionOverview(session: session): - self.openSessionOverview(session: session) + case let .openSessionOverview(sessionInfo: sessionInfo): + self.openSessionOverview(session: sessionInfo) } } return coordinator @@ -75,7 +75,7 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { coordinator.completion = { [weak self] result in guard let self = self else { return } switch result { - case let .openSessionDetails(session: session): + case let .openSessionDetails(sessionInfo: session): self.openSessionDetails(session: session) } } @@ -83,7 +83,7 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { } private func createUserSessionOverviewCoordinator(session: UserSessionInfo) -> UserSessionOverviewCoordinator { - let parameters = UserSessionOverviewCoordinatorParameters(session: session) + let parameters = UserSessionOverviewCoordinatorParameters(session: self.parameters.session, sessionInfo: session) return UserSessionOverviewCoordinator(parameters: parameters) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift index d1aeae991..cc6507213 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift @@ -18,7 +18,8 @@ import CommonKit import SwiftUI struct UserSessionOverviewCoordinatorParameters { - let session: UserSessionInfo + let session: MXSession + let sessionInfo: UserSessionInfo } final class UserSessionOverviewCoordinator: Coordinator, Presentable { @@ -40,7 +41,8 @@ final class UserSessionOverviewCoordinator: Coordinator, Presentable { init(parameters: UserSessionOverviewCoordinatorParameters) { self.parameters = parameters - viewModel = UserSessionOverviewViewModel(session: parameters.session) + let service = UserSessionOverviewService(session: parameters.session, sessionInfo: parameters.sessionInfo) + viewModel = UserSessionOverviewViewModel(sessionInfo: parameters.sessionInfo, service: service) hostingController = VectorHostingController(rootView: UserSessionOverview(viewModel: viewModel.context)) @@ -57,8 +59,8 @@ final class UserSessionOverviewCoordinator: Coordinator, Presentable { switch result { case .verifyCurrentSession: break // TODO: - case let .showSessionDetails(session: session): - self.completion?(.openSessionDetails(session: session)) + case let .showSessionDetails(sessionInfo: sessionInfo): + self.completion?(.openSessionDetails(sessionInfo: sessionInfo)) } } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift index aa0d437b0..aa505c244 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift @@ -25,7 +25,9 @@ enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { // mock that screen. case currentSession case otherSession - + case sessionWithPushNotifications(enabled: Bool) + case remotelyTogglingPushersNotAvailable + /// The associated screen var screenType: Any.Type { UserSessionOverview.self @@ -33,12 +35,17 @@ enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { /// A list of screen state definitions static var allCases: [MockUserSessionOverviewScreenState] { - [.currentSession, .otherSession] + [.currentSession, + .otherSession, + .sessionWithPushNotifications(enabled: true), + .sessionWithPushNotifications(enabled: false), + .remotelyTogglingPushersNotAvailable] } /// Generate the view struct for the screen state. var screenView: ([Any], AnyView) { let session: UserSessionInfo + let service: UserSessionOverviewServiceProtocol switch self { case .currentSession: session = UserSessionInfo(id: "alice", @@ -56,6 +63,7 @@ enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { deviceName: "My iPhone", isActive: true, isCurrent: true) + service = MockUserSessionOverviewService() case .otherSession: session = UserSessionInfo(id: "1", name: "macOS", @@ -72,9 +80,44 @@ enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { deviceName: "My Mac", isActive: false, isCurrent: false) + service = MockUserSessionOverviewService() + case .sessionWithPushNotifications(let enabled): + session = UserSessionInfo(id: "1", + name: "macOS", + deviceType: .desktop, + isVerified: true, + lastSeenIP: "1.0.0.1", + lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000, + applicationName: "Element MacOS", + applicationVersion: "1.0.0", + applicationURL: nil, + deviceModel: nil, + deviceOS: "macOS 12.5.1", + lastSeenIPLocation: nil, + deviceName: "My Mac", + isActive: false, + isCurrent: false) + service = MockUserSessionOverviewService(pusherEnabled: enabled) + case .remotelyTogglingPushersNotAvailable: + session = UserSessionInfo(id: "1", + name: "macOS", + deviceType: .desktop, + isVerified: true, + lastSeenIP: "1.0.0.1", + lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000, + applicationName: "Element MacOS", + applicationVersion: "1.0.0", + applicationURL: nil, + deviceModel: nil, + deviceOS: "macOS 12.5.1", + lastSeenIPLocation: nil, + deviceName: "My Mac", + isActive: false, + isCurrent: false) + service = MockUserSessionOverviewService(pusherEnabled: true, remotelyTogglingPushersAvailable: false) } - let viewModel = UserSessionOverviewViewModel(session: session) + let viewModel = UserSessionOverviewViewModel(sessionInfo: session, service: service) // can simulate service and viewModel actions here if needs be. return ([viewModel], AnyView(UserSessionOverview(viewModel: viewModel.context))) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Service/MatrixSDK/UserSessionOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Service/MatrixSDK/UserSessionOverviewService.swift new file mode 100644 index 000000000..45485dc0f --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Service/MatrixSDK/UserSessionOverviewService.swift @@ -0,0 +1,106 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import MatrixSDK + +class UserSessionOverviewService: UserSessionOverviewServiceProtocol { + + // MARK: - Members + + private(set) var pusherEnabledSubject: CurrentValueSubject + private(set) var remotelyTogglingPushersAvailableSubject: CurrentValueSubject + + // MARK: - Private + + private let session: MXSession + private let sessionInfo: UserSessionInfo + private var pusher: MXPusher? + + // MARK: - Setup + + init(session: MXSession, sessionInfo: UserSessionInfo) { + self.session = session + self.sessionInfo = sessionInfo + self.pusherEnabledSubject = CurrentValueSubject(nil) + self.remotelyTogglingPushersAvailableSubject = CurrentValueSubject(false) + + checkServerVersions { [weak self] in + self?.checkPusher() + } + } + + // MARK: - UserSessionOverviewServiceProtocol + + func togglePushNotifications() { + guard let pusher = pusher, let enabled = pusher.enabled?.boolValue, self.remotelyTogglingPushersAvailableSubject.value else { + return + } + + let data = pusher.data.jsonDictionary() as? [String: Any] ?? [:] + + self.session.matrixRestClient .setPusher(pushKey: pusher.pushkey, kind: MXPusherKind.from(value: pusher.kind), appId: pusher.appId, appDisplayName:pusher.appDisplayName, deviceDisplayName: pusher.deviceDisplayName, profileTag: pusher.profileTag ?? "", lang: pusher.lang, data: data, append: false, enabled: !enabled) { [weak self] response in + guard let self = self else { return } + + switch response { + case .success: + self.checkPusher() + case .failure(let error): + MXLog.warning("[UserSessionOverviewService] togglePushNotifications failed due to error: \(error)") + self.pusherEnabledSubject.send(enabled) + } + } + } + + // MARK: - Private + + private func checkServerVersions(_ completion: @escaping () -> Void) { + session.supportedMatrixVersions { [weak self] response in + switch response { + case .success(let versions): + self?.remotelyTogglingPushersAvailableSubject.send(versions.supportsRemotelyTogglingPushNotifications) + case .failure(let error): + MXLog.warning("[UserSessionOverviewService] checkServerVersions failed due to error: \(error)") + } + completion() + } + } + + private func checkPusher() { + session.matrixRestClient.pushers { [weak self] response in + switch response { + case .success(let pushers): + self?.check(pushers: pushers) + case .failure(let error): + MXLog.warning("[UserSessionOverviewService] checkPusher failed due to error: \(error)") + } + } + } + + private func check(pushers: [MXPusher]) { + for pusher in pushers where pusher.deviceId == sessionInfo.id { + self.pusher = pusher + + guard let enabled = pusher.enabled else { + // For backwards compatibility, any pusher without an enabled field should be treated as if enabled is false + pusherEnabledSubject.send(false) + return + } + + pusherEnabledSubject.send(enabled.boolValue) + } + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Service/Mock/MockUserSessionOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Service/Mock/MockUserSessionOverviewService.swift new file mode 100644 index 000000000..ccd6f63dd --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Service/Mock/MockUserSessionOverviewService.swift @@ -0,0 +1,38 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import Foundation + +class MockUserSessionOverviewService: UserSessionOverviewServiceProtocol { + + + var pusherEnabledSubject: CurrentValueSubject + var remotelyTogglingPushersAvailableSubject: CurrentValueSubject + + init(pusherEnabled: Bool? = nil, remotelyTogglingPushersAvailable: Bool = true) { + self.pusherEnabledSubject = CurrentValueSubject(pusherEnabled) + self.remotelyTogglingPushersAvailableSubject = CurrentValueSubject(remotelyTogglingPushersAvailable) + } + + func togglePushNotifications() { + guard let enabled = pusherEnabledSubject.value, self.remotelyTogglingPushersAvailableSubject.value else { + return + } + + pusherEnabledSubject.send(!enabled) + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Service/UserSessionOverviewServiceProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Service/UserSessionOverviewServiceProtocol.swift new file mode 100644 index 000000000..807f50c17 --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Service/UserSessionOverviewServiceProtocol.swift @@ -0,0 +1,25 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import Foundation + +protocol UserSessionOverviewServiceProtocol { + var pusherEnabledSubject: CurrentValueSubject { get } + var remotelyTogglingPushersAvailableSubject: CurrentValueSubject { get } + + func togglePushNotifications() +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift index 2133c445b..7dfb4d04f 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift @@ -34,4 +34,31 @@ class UserSessionOverviewUITests: MockScreenTestCase { app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.currentSession.title) XCTAssertTrue(app.buttons[VectorL10n.userSessionOverviewSessionDetailsButtonTitle].exists) } + + func test_whenSessionOverviewPresented_pusherEnabledToggleExists() { + app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.sessionWithPushNotifications(enabled: true).title) + XCTAssertTrue(app.switches["UserSessionOverviewToggleCell"].exists) + XCTAssertTrue(app.switches["UserSessionOverviewToggleCell"].isOn) + XCTAssertTrue(app.switches["UserSessionOverviewToggleCell"].isEnabled) + XCTAssertTrue(app.staticTexts[VectorL10n.userSessionPushNotifications].exists) + XCTAssertTrue(app.staticTexts[VectorL10n.userSessionPushNotificationsMessage].exists) + } + + func test_whenSessionOverviewPresented_pusherDisabledToggleExists() { + app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.sessionWithPushNotifications(enabled: false).title) + XCTAssertTrue(app.switches["UserSessionOverviewToggleCell"].exists) + XCTAssertFalse(app.switches["UserSessionOverviewToggleCell"].isOn) + XCTAssertTrue(app.switches["UserSessionOverviewToggleCell"].isEnabled) + XCTAssertTrue(app.staticTexts[VectorL10n.userSessionPushNotifications].exists) + XCTAssertTrue(app.staticTexts[VectorL10n.userSessionPushNotificationsMessage].exists) + } + + func test_whenSessionOverviewPresented_pusherEnabledToggleExists_remotelyTogglingPushersAvailable() { + app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.remotelyTogglingPushersNotAvailable.title) + XCTAssertTrue(app.switches["UserSessionOverviewToggleCell"].exists) + XCTAssertTrue(app.switches["UserSessionOverviewToggleCell"].isOn) + XCTAssertFalse(app.switches["UserSessionOverviewToggleCell"].isEnabled) + XCTAssertTrue(app.staticTexts[VectorL10n.userSessionPushNotifications].exists) + XCTAssertTrue(app.staticTexts[VectorL10n.userSessionPushNotificationsMessage].exists) + } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift index a89e3f96d..416b0e032 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift @@ -23,8 +23,9 @@ class UserSessionOverviewViewModelTests: XCTestCase { var sut: UserSessionOverviewViewModel! func test_whenVerifyCurrentSessionProcessed_completionWithVerifyCurrentSessionCalled() { - sut = UserSessionOverviewViewModel(session: createUserSessionInfo()) + sut = UserSessionOverviewViewModel(sessionInfo: createUserSessionInfo(), service: MockUserSessionOverviewService()) + XCTAssertEqual(sut.state.isPusherEnabled, nil) var modelResult: UserSessionOverviewViewModelResult? sut.completion = { result in modelResult = result @@ -34,17 +35,57 @@ class UserSessionOverviewViewModelTests: XCTestCase { } func test_whenViewSessionDetailsProcessed_completionWithShowSessionDetailsCalled() { - let session = createUserSessionInfo() - sut = UserSessionOverviewViewModel(session: session) + let sessionInfo = createUserSessionInfo() + sut = UserSessionOverviewViewModel(sessionInfo: sessionInfo, service: MockUserSessionOverviewService()) + XCTAssertEqual(sut.state.isPusherEnabled, nil) var modelResult: UserSessionOverviewViewModelResult? sut.completion = { result in modelResult = result } sut.process(viewAction: .viewSessionDetails) - XCTAssertEqual(modelResult, .showSessionDetails(session: session)) + XCTAssertEqual(modelResult, .showSessionDetails(sessionInfo: sessionInfo)) } + func test_whenViewSessionDetailsProcessed_toggleAvailablePusher() { + let sessionInfo = createUserSessionInfo() + let service = MockUserSessionOverviewService(pusherEnabled: true) + sut = UserSessionOverviewViewModel(sessionInfo: sessionInfo, service: service) + + XCTAssertTrue(sut.state.remotelyTogglingPushersAvailable) + XCTAssertEqual(sut.state.isPusherEnabled, true) + sut.process(viewAction: .togglePushNotifications) + XCTAssertEqual(sut.state.isPusherEnabled, false) + sut.process(viewAction: .togglePushNotifications) + XCTAssertEqual(sut.state.isPusherEnabled, true) + } + + func test_whenViewSessionDetailsProcessed_toggleNoPusher() { + let sessionInfo = createUserSessionInfo() + let service = MockUserSessionOverviewService(pusherEnabled: nil) + sut = UserSessionOverviewViewModel(sessionInfo: sessionInfo, service: service) + + XCTAssertTrue(sut.state.remotelyTogglingPushersAvailable) + XCTAssertEqual(sut.state.isPusherEnabled, nil) + sut.process(viewAction: .togglePushNotifications) + XCTAssertEqual(sut.state.isPusherEnabled, nil) + sut.process(viewAction: .togglePushNotifications) + XCTAssertEqual(sut.state.isPusherEnabled, nil) + } + + func test_whenViewSessionDetailsProcessed_remotelyTogglingPushersNotAvailable() { + let sessionInfo = createUserSessionInfo() + let service = MockUserSessionOverviewService(pusherEnabled: true, remotelyTogglingPushersAvailable: false) + sut = UserSessionOverviewViewModel(sessionInfo: sessionInfo, service: service) + + XCTAssertFalse(sut.state.remotelyTogglingPushersAvailable) + XCTAssertEqual(sut.state.isPusherEnabled, true) + sut.process(viewAction: .togglePushNotifications) + XCTAssertEqual(sut.state.isPusherEnabled, true) + sut.process(viewAction: .togglePushNotifications) + XCTAssertEqual(sut.state.isPusherEnabled, true) + } + private func createUserSessionInfo() -> UserSessionInfo { UserSessionInfo(id: "session", name: "iOS", diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift index deddfc31c..366da4f71 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift @@ -19,13 +19,13 @@ import Foundation // MARK: - Coordinator enum UserSessionOverviewCoordinatorResult { - case openSessionDetails(session: UserSessionInfo) + case openSessionDetails(sessionInfo: UserSessionInfo) } // MARK: View model enum UserSessionOverviewViewModelResult: Equatable { - case showSessionDetails(session: UserSessionInfo) + case showSessionDetails(sessionInfo: UserSessionInfo) case verifyCurrentSession } @@ -34,9 +34,13 @@ enum UserSessionOverviewViewModelResult: Equatable { struct UserSessionOverviewViewState: BindableState { let cardViewData: UserSessionCardViewData let isCurrentSession: Bool + var isPusherEnabled: Bool? + var remotelyTogglingPushersAvailable: Bool + var showLoadingIndicator: Bool } enum UserSessionOverviewViewAction { case verifyCurrentSession case viewSessionDetails + case togglePushNotifications } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift index 42eaaa6c7..a5c8f7d99 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift @@ -19,18 +19,45 @@ import SwiftUI typealias UserSessionOverviewViewModelType = StateStoreViewModel class UserSessionOverviewViewModel: UserSessionOverviewViewModelType, UserSessionOverviewViewModelProtocol { - private let session: UserSessionInfo + private let sessionInfo: UserSessionInfo + private let service: UserSessionOverviewServiceProtocol var completion: ((UserSessionOverviewViewModelResult) -> Void)? - init(session: UserSessionInfo) { - self.session = session + // MARK: - Setup + + init(sessionInfo: UserSessionInfo, service: UserSessionOverviewServiceProtocol) { + self.sessionInfo = sessionInfo + self.service = service - let cardViewData = UserSessionCardViewData(session: session) - let state = UserSessionOverviewViewState(cardViewData: cardViewData, isCurrentSession: session.isCurrent) + let cardViewData = UserSessionCardViewData(sessionInfo: sessionInfo) + let state = UserSessionOverviewViewState(cardViewData: cardViewData, + isCurrentSession: sessionInfo.isCurrent, + isPusherEnabled: service.pusherEnabledSubject.value, + remotelyTogglingPushersAvailable: service.remotelyTogglingPushersAvailableSubject.value, + showLoadingIndicator: false) super.init(initialViewState: state) + + startObservingService() } + private func startObservingService() { + service + .pusherEnabledSubject + .sink(receiveValue: { [weak self] pushEnabled in + self?.state.showLoadingIndicator = false + self?.state.isPusherEnabled = pushEnabled + }) + .store(in: &cancellables) + + service + .remotelyTogglingPushersAvailableSubject + .sink(receiveValue: { [weak self] remotelyTogglingPushersAvailable in + self?.state.remotelyTogglingPushersAvailable = remotelyTogglingPushersAvailable + }) + .store(in: &cancellables) + } + // MARK: - Public override func process(viewAction: UserSessionOverviewViewAction) { @@ -38,7 +65,10 @@ class UserSessionOverviewViewModel: UserSessionOverviewViewModelType, UserSessio case .verifyCurrentSession: completion?(.verifyCurrentSession) case .viewSessionDetails: - completion?(.showSessionDetails(session: session)) + completion?(.showSessionDetails(sessionInfo: sessionInfo)) + case .togglePushNotifications: + self.state.showLoadingIndicator = true + service.togglePushNotifications() } } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift index 8ebf47abc..b2170f0d2 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift @@ -34,10 +34,18 @@ struct UserSessionOverview: View { UserSessionOverviewDisclosureCell(title: VectorL10n.userSessionOverviewSessionDetailsButtonTitle, onBackgroundTap: { viewModel.send(viewAction: .viewSessionDetails) }) + if let enabled = viewModel.viewState.isPusherEnabled { + UserSessionOverviewToggleCell(title: VectorL10n.userSessionPushNotifications, + message: VectorL10n.userSessionPushNotificationsMessage, + isOn: enabled, isEnabled: viewModel.viewState.remotelyTogglingPushersAvailable) { + viewModel.send(viewAction: .togglePushNotifications) + } + } } } .background(theme.colors.system.ignoresSafeArea()) .frame(maxHeight: .infinity) + .waitOverlay(show: viewModel.viewState.showLoadingIndicator, allowUserInteraction: false) .navigationTitle(viewModel.viewState.isCurrentSession ? VectorL10n.userSessionOverviewCurrentSessionTitle : VectorL10n.userSessionOverviewSessionTitle) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift index e4146e9a7..6fa8b00ca 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift @@ -33,7 +33,7 @@ struct UserSessionOverviewDisclosureCell: View { .frame(maxWidth: .infinity, alignment: .leading) Image(Asset.Images.chevron.name) } - .padding(.vertical, 12) + .padding(.vertical, 15) .padding(.horizontal, 16) SeparatorLine() } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewToggleCell.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewToggleCell.swift new file mode 100644 index 000000000..b6f79b58f --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewToggleCell.swift @@ -0,0 +1,86 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct UserSessionOverviewToggleCell: View { + @Environment(\.theme) private var theme: ThemeSwiftUI + + let title: String + let message: String? + let isOn: Bool + let isEnabled: Bool + var onBackgroundTap: (() -> Void)? + + var body: some View { + VStack(spacing: 8) { + Button(action: { + guard isEnabled else { return } + onBackgroundTap?() + }) { + VStack(spacing: 0) { + SeparatorLine() + Toggle(isOn: .constant(isOn)) { + Text(title) + .font(theme.fonts.body) + .foregroundColor(theme.colors.primaryContent) + .opacity(isEnabled ? 1 : 0.3) + } + .disabled(!isEnabled) + .allowsHitTesting(false) + .padding(.vertical, 10) + .padding(.horizontal, 16) + .accessibilityIdentifier("UserSessionOverviewToggleCell") + SeparatorLine() + } + .background(theme.colors.background) + } + .disabled(!isEnabled) + if let message = message { + Text(message) + .multilineTextAlignment(.leading) + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.secondaryContent) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 16) + } + } + } +} + +struct UserSessionOverviewToggleCell_Previews: PreviewProvider { + static var previews: some View { + Group { + preview + .theme(.light) + .preferredColorScheme(.light) + preview + .theme(.dark) + .preferredColorScheme(.dark) + } + } + + static var preview: some View { + VStack { + UserSessionOverviewToggleCell(title: "Title", message: nil, isOn: true, isEnabled: true) + UserSessionOverviewToggleCell(title: "Title", message: nil, isOn: false, isEnabled: true) + UserSessionOverviewToggleCell(title: "Title", message: "some very long message text in order to test the multine alignment", isOn: true, isEnabled: true) + UserSessionOverviewToggleCell(title: "Title", message: nil, isOn: true, isEnabled: false) + UserSessionOverviewToggleCell(title: "Title", message: nil, isOn: false, isEnabled: false) + UserSessionOverviewToggleCell(title: "Title", message: "some very long message text in order to test the multine alignment", isOn: true, isEnabled: false) + } + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift index c32202059..1a72a45c6 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift @@ -59,12 +59,12 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { self.showAllInactiveSessions() case .verifyCurrentSession: self.startVerifyCurrentSession() - case let .showCurrentSessionOverview(session): - self.showCurrentSessionOverview(session: session) + case let .showCurrentSessionOverview(sessionInfo): + self.showCurrentSessionOverview(sessionInfo: sessionInfo) case .showAllOtherSessions: self.showAllOtherSessions() - case let .showUserSessionOverview(session): - self.showUserSessionOverview(session: session) + case let .showUserSessionOverview(sessionInfo): + self.showUserSessionOverview(sessionInfo: sessionInfo) } } } @@ -100,12 +100,12 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { // TODO: } - private func showCurrentSessionOverview(session: UserSessionInfo) { - completion?(.openSessionOverview(session: session)) + private func showCurrentSessionOverview(sessionInfo: UserSessionInfo) { + completion?(.openSessionOverview(sessionInfo: sessionInfo)) } - private func showUserSessionOverview(session: UserSessionInfo) { - completion?(.openSessionOverview(session: session)) + private func showUserSessionOverview(sessionInfo: UserSessionInfo) { + completion?(.openSessionOverview(sessionInfo: sessionInfo)) } private func showAllOtherSessions() { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProvider.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProvider.swift index 5ddf57bde..7ab1f5a04 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProvider.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProvider.swift @@ -40,7 +40,7 @@ class UserSessionsDataProvider: UserSessionsDataProviderProtocol { session.matrixRestClient.devices(completion: completion) } - func device(withDeviceId deviceId: String, ofUser userId: String) -> MXDeviceInfo { + func device(withDeviceId deviceId: String, ofUser userId: String) -> MXDeviceInfo? { session.crypto.device(withDeviceId: deviceId, ofUser: userId) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProviderProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProviderProtocol.swift index 8537f93fa..ca65e218e 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProviderProtocol.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProviderProtocol.swift @@ -26,7 +26,7 @@ protocol UserSessionsDataProviderProtocol { func devices(completion: @escaping (MXResponse<[MXDevice]>) -> Void) - func device(withDeviceId deviceId: String, ofUser userId: String) -> MXDeviceInfo + func device(withDeviceId deviceId: String, ofUser userId: String) -> MXDeviceInfo? func accountData(for eventType: String) -> [AnyHashable: Any]? } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift index 2b3ae080a..812554a3c 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift @@ -19,7 +19,7 @@ import Foundation // MARK: - Coordinator enum UserSessionsOverviewCoordinatorResult { - case openSessionOverview(session: UserSessionInfo) + case openSessionOverview(sessionInfo: UserSessionInfo) } // MARK: View model diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift index 65fefaf7e..c44c2b6fa 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift @@ -68,7 +68,7 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess state.otherSessionsViewData = userSessionsViewData.otherSessions.asViewData() if let currentSessionInfo = userSessionsViewData.currentSession { - state.currentSessionViewData = UserSessionCardViewData(session: currentSessionInfo) + state.currentSessionViewData = UserSessionCardViewData(sessionInfo: currentSessionInfo) } } diff --git a/changelog.d/6787.change b/changelog.d/6787.change new file mode 100644 index 000000000..b4defa9d7 --- /dev/null +++ b/changelog.d/6787.change @@ -0,0 +1 @@ +User sessions: Add support for MSC3881 From d04cdac7594d5b6304f6728ae1c32045790e27b5 Mon Sep 17 00:00:00 2001 From: Doug Date: Fri, 23 Sep 2022 16:37:26 +0100 Subject: [PATCH 093/104] Add missing accessibility labels on All Chats buttons. --- Riot/Assets/en.lproj/Vector.strings | 1 + Riot/Generated/Strings.swift | 4 ++++ Riot/Modules/Home/AllChats/AllChatsCoordinator.swift | 1 + Riot/Modules/Home/AllChats/AllChatsViewController.swift | 6 +++++- changelog.d/6580.bugfix | 1 + 5 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 changelog.d/6580.bugfix diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 6449e8fbd..df4634d38 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2195,6 +2195,7 @@ Tap the + to start adding people."; "room_recents_recently_viewed_section" = "Recently viewed"; +"all_chats_user_menu_accessibility_label" = "User menu"; "all_chats_user_menu_settings" = "User settings"; "all_chats_edit_menu_leave_space" = "Leave %@"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 65bcd2560..10bf86039 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -247,6 +247,10 @@ public class VectorL10n: NSObject { public static var allChatsTitle: String { return VectorL10n.tr("Vector", "all_chats_title") } + /// User menu + public static var allChatsUserMenuAccessibilityLabel: String { + return VectorL10n.tr("Vector", "all_chats_user_menu_accessibility_label") + } /// User settings public static var allChatsUserMenuSettings: String { return VectorL10n.tr("Vector", "all_chats_user_menu_settings") diff --git a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift index 27af3a94b..a4561437a 100644 --- a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift +++ b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift @@ -370,6 +370,7 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { button.menu = menu button.showsMenuAsPrimaryAction = true button.autoresizingMask = [.flexibleHeight, .flexibleWidth] + button.accessibilityLabel = VectorL10n.allChatsUserMenuAccessibilityLabel view.addSubview(button) self.avatarMenuButton = button diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index 30a9ee6cc..b2cbc8ce8 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -514,8 +514,12 @@ class AllChatsViewController: HomeViewController { self.isToolbarHidden = false self.update(with: ThemeService.shared().theme) + + let spacesButton = UIBarButtonItem(image: Asset.Images.allChatsSpacesIcon.image, style: .done, target: self, action: #selector(self.showSpaceSelectorAction(sender: ))) + spacesButton.accessibilityLabel = VectorL10n.spaceSelectorTitle + self.toolbar.items = [ - UIBarButtonItem(image: Asset.Images.allChatsSpacesIcon.image, style: .done, target: self, action: #selector(self.showSpaceSelectorAction(sender: ))), + spacesButton, UIBarButtonItem.flexibleSpace(), UIBarButtonItem(image: Asset.Images.allChatsEditIcon.image, menu: menu) ] diff --git a/changelog.d/6580.bugfix b/changelog.d/6580.bugfix new file mode 100644 index 000000000..1ca94a1df --- /dev/null +++ b/changelog.d/6580.bugfix @@ -0,0 +1 @@ +Voiceover: Add labels to User Menu and My Spaces buttons on the All Chats view. From d5b8f43f59628a9376304e1c4c4dabffc0111c4b Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Mon, 3 Oct 2022 15:33:35 +0200 Subject: [PATCH 094/104] Added support for MSC3881 - Update after review --- Riot/Managers/Call/CallPresenter.swift | 6 ++--- .../UserSessionsFlowCoordinator.swift | 22 +++++++++---------- .../UserSessionOverviewService.swift | 11 +++++++++- .../UserSessionsOverviewServiceTests.swift | 2 +- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/Riot/Managers/Call/CallPresenter.swift b/Riot/Managers/Call/CallPresenter.swift index 683a791dd..9d9465f8b 100644 --- a/Riot/Managers/Call/CallPresenter.swift +++ b/Riot/Managers/Call/CallPresenter.swift @@ -455,11 +455,11 @@ class CallPresenter: NSObject { #if canImport(JitsiMeetSDK) JMCallKitProxy.addListener(self) - guard let sessionInfo = sessions.first else { + guard let session = sessions.first else { return } - widgetEventsListener = sessionInfo.listenToEvents([ + widgetEventsListener = session.listenToEvents([ MXEventType(identifier: kWidgetMatrixEventTypeString), MXEventType(identifier: kWidgetModularEventTypeString) ]) { (event, direction, _) in @@ -468,7 +468,7 @@ class CallPresenter: NSObject { return } - self.processWidgetEvent(event, inSession: sessionInfo) + self.processWidgetEvent(event, inSession: session) } #endif } diff --git a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift index 64cf32f40..83dc0361d 100644 --- a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift @@ -54,36 +54,36 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { guard let self = self else { return } switch result { case let .openSessionOverview(sessionInfo: sessionInfo): - self.openSessionOverview(session: sessionInfo) + self.openSessionOverview(sessionInfo: sessionInfo) } } return coordinator } - private func openSessionDetails(session: UserSessionInfo) { - let coordinator = createUserSessionDetailsCoordinator(session: session) + private func openSessionDetails(sessionInfo: UserSessionInfo) { + let coordinator = createUserSessionDetailsCoordinator(sessionInfo: sessionInfo) pushScreen(with: coordinator) } - private func createUserSessionDetailsCoordinator(session: UserSessionInfo) -> UserSessionDetailsCoordinator { - let parameters = UserSessionDetailsCoordinatorParameters(session: session) + private func createUserSessionDetailsCoordinator(sessionInfo: UserSessionInfo) -> UserSessionDetailsCoordinator { + let parameters = UserSessionDetailsCoordinatorParameters(session: sessionInfo) return UserSessionDetailsCoordinator(parameters: parameters) } - private func openSessionOverview(session: UserSessionInfo) { - let coordinator = createUserSessionOverviewCoordinator(session: session) + private func openSessionOverview(sessionInfo: UserSessionInfo) { + let coordinator = createUserSessionOverviewCoordinator(sessionInfo: sessionInfo) coordinator.completion = { [weak self] result in guard let self = self else { return } switch result { - case let .openSessionDetails(sessionInfo: session): - self.openSessionDetails(session: session) + case let .openSessionDetails(sessionInfo: sessionInfo): + self.openSessionDetails(sessionInfo: sessionInfo) } } pushScreen(with: coordinator) } - private func createUserSessionOverviewCoordinator(session: UserSessionInfo) -> UserSessionOverviewCoordinator { - let parameters = UserSessionOverviewCoordinatorParameters(session: self.parameters.session, sessionInfo: session) + private func createUserSessionOverviewCoordinator(sessionInfo: UserSessionInfo) -> UserSessionOverviewCoordinator { + let parameters = UserSessionOverviewCoordinatorParameters(session: self.parameters.session, sessionInfo: sessionInfo) return UserSessionOverviewCoordinator(parameters: parameters) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Service/MatrixSDK/UserSessionOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Service/MatrixSDK/UserSessionOverviewService.swift index 45485dc0f..170e2ef2c 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Service/MatrixSDK/UserSessionOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Service/MatrixSDK/UserSessionOverviewService.swift @@ -52,7 +52,16 @@ class UserSessionOverviewService: UserSessionOverviewServiceProtocol { let data = pusher.data.jsonDictionary() as? [String: Any] ?? [:] - self.session.matrixRestClient .setPusher(pushKey: pusher.pushkey, kind: MXPusherKind.from(value: pusher.kind), appId: pusher.appId, appDisplayName:pusher.appDisplayName, deviceDisplayName: pusher.deviceDisplayName, profileTag: pusher.profileTag ?? "", lang: pusher.lang, data: data, append: false, enabled: !enabled) { [weak self] response in + self.session.matrixRestClient.setPusher(pushKey: pusher.pushkey, + kind: MXPusherKind(value: pusher.kind), + appId: pusher.appId, + appDisplayName:pusher.appDisplayName, + deviceDisplayName: pusher.deviceDisplayName, + profileTag: pusher.profileTag ?? "", + lang: pusher.lang, + data: data, + append: false, + enabled: !enabled) { [weak self] response in guard let self = self else { return } switch response { diff --git a/RiotTests/UserSessionsOverviewServiceTests.swift b/RiotTests/UserSessionsOverviewServiceTests.swift index 1c7e50d5d..d52ca31d9 100644 --- a/RiotTests/UserSessionsOverviewServiceTests.swift +++ b/RiotTests/UserSessionsOverviewServiceTests.swift @@ -155,7 +155,7 @@ private class MockUserSessionsDataProvider: UserSessionsDataProviderProtocol { } } - func device(withDeviceId deviceId: String, ofUser userId: String) -> MXDeviceInfo { + func device(withDeviceId deviceId: String, ofUser userId: String) -> MXDeviceInfo? { guard deviceId == currentDeviceId else { return MockDeviceInfo(verified: deviceId != unverifiedDeviceId) } From 8903fde833b8cb6f43d25c2cbc8a6095dcb8ea78 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Tue, 4 Oct 2022 10:49:26 +0300 Subject: [PATCH 095/104] Include browser version in parsed user agent (PSG-761) (#6788) * Update UserSessionInfo structure to include client version * Add string for browser * Update user agent parser to parse browser version too * Add browser row into the session details * Add changelog * Fix tests * Run Swift format --- Riot/Assets/en.lproj/Vector.strings | 1 + Riot/Generated/Strings.swift | 4 ++ .../UserSessions/Common/UserAgentParser.swift | 48 ++++++++++++----- .../UserSessions/Common/UserSessionInfo.swift | 7 ++- .../Common/View/UserSessionCardView.swift | 3 +- .../MockUserSessionDetailsScreenState.swift | 6 ++- .../UserSessionDetailsViewModelTests.swift | 6 ++- .../UserSessionDetailsViewModel.swift | 6 +++ .../MockUserSessionOverviewScreenState.swift | 6 ++- .../UserSessionOverviewViewModelTests.swift | 5 +- .../MatrixSDK/UserSessionsDataProvider.swift | 4 +- .../UserSessionsDataProviderProtocol.swift | 2 +- .../UserSessionsOverviewService.swift | 3 +- .../MockUserSessionsOverviewService.swift | 12 +++-- RiotTests/UserAgentParserTests.swift | 54 ++++++++++--------- changelog.d/pr-6788.change | 1 + 16 files changed, 109 insertions(+), 59 deletions(-) create mode 100644 changelog.d/pr-6788.change diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 8cf427e04..cb141e966 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2411,6 +2411,7 @@ To enable access, tap Settings> Location and select Always"; "user_session_details_device_ip_address" = "IP address"; "user_session_details_device_ip_location" = "IP location"; "user_session_details_device_model" = "Model"; +"user_session_details_device_browser" = "Browser"; "user_session_details_device_os" = "Operating System"; "user_session_details_application_name" = "Name"; "user_session_details_application_version" = "Version"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 2e02e0954..f8c0cc121 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -8499,6 +8499,10 @@ public class VectorL10n: NSObject { public static var userSessionDetailsApplicationVersion: String { return VectorL10n.tr("Vector", "user_session_details_application_version") } + /// Browser + public static var userSessionDetailsDeviceBrowser: String { + return VectorL10n.tr("Vector", "user_session_details_device_browser") + } /// IP address public static var userSessionDetailsDeviceIpAddress: String { return VectorL10n.tr("Vector", "user_session_details_device_ip_address") diff --git a/RiotSwiftUI/Modules/UserSessions/Common/UserAgentParser.swift b/RiotSwiftUI/Modules/UserSessions/Common/UserAgentParser.swift index d4fe29466..2c0a10487 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/UserAgentParser.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/UserAgentParser.swift @@ -125,18 +125,28 @@ enum UserAgentParser { // Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) ElementNightly/2022091301 Chrome/104.0.5112.102 Electron/20.1.1 Safari/537.36 private static func parseDesktop(_ userAgent: String) -> UserAgent { var deviceOS: String? - let browserName = browserName(for: userAgent) + let browserInfo = browserInfo(for: userAgent) if let deviceInfo = findFirstDeviceInfo(in: userAgent) { let deviceInfoComponents = deviceInfo.components(separatedBy: "; ") - deviceOS = deviceInfoComponents[safe: 1]?.hasPrefix("Android") == true ? deviceInfoComponents[safe: 1] : deviceInfoComponents.first + if deviceInfoComponents[safe: 1]?.hasPrefix("Android") == true { + deviceOS = deviceInfoComponents[safe: 1] + } else if deviceInfoComponents.first == "Macintosh" { + var osFull = deviceInfoComponents[safe: 1] + osFull = osFull?.replacingOccurrences(of: "Intel ", with: "") + osFull = osFull?.replacingOccurrences(of: "Mac OS X", with: "macOS") + osFull = osFull?.replacingOccurrences(of: "_", with: ".") + deviceOS = osFull + } else { + deviceOS = deviceInfoComponents.first + } } return UserAgent(deviceType: .desktop, - deviceModel: browserName, + deviceModel: nil, deviceOS: deviceOS, - clientName: nil, - clientVersion: nil) + clientName: browserInfo.name, + clientVersion: browserInfo.version) } // Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 @@ -164,20 +174,30 @@ enum UserAgentParser { return nil } - private static func browserName(for userAgent: String) -> String? { + private static func browserInfo(for userAgent: String) -> (name: String?, version: String?) { let components = userAgent.components(separatedBy: " ") if components.last?.hasPrefix("Firefox") == true { - return "Firefox" - } else if components.last?.hasPrefix("Safari") == true - && components[safe:components.count - 2]?.hasPrefix("Mobile") == true { + let version = components.last?.components(separatedBy: "/").last + return ("Firefox", version) + } else if components.last?.hasPrefix("Safari") == true, + components[safe: components.count - 2]?.hasPrefix("Mobile") == true { // mobile browser - let possibleBrowserName = components[safe:components.count - 3]?.components(separatedBy: "/").first - return possibleBrowserName == "Version" ? "Safari" : possibleBrowserName - } else if components.last?.hasPrefix("Safari") == true && components[safe:components.count - 2]?.hasPrefix("Version") == true { - return "Safari" + let possibleBrowserComponents = components[safe: components.count - 3] + if possibleBrowserComponents?.hasPrefix("Version") == true { + let version = possibleBrowserComponents?.components(separatedBy: "/").last + return ("Safari", version) + } else { + let components = possibleBrowserComponents?.components(separatedBy: "/") + return (components?.first, components?.last) + } + } else if components.last?.hasPrefix("Safari") == true, components[safe: components.count - 2]?.hasPrefix("Version") == true { + let version = components[safe: components.count - 2]?.components(separatedBy: "/").last + return ("Safari", version) } else { // regular browser - return components[safe:components.count - 2]?.components(separatedBy: "/").first + let browserComponent = components[safe: components.count - 2] + let components = browserComponent?.components(separatedBy: "/") + return (components?.first, components?[safe: 1]) } } } diff --git a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift index 5bfbe2332..79fe49ec2 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/UserSessionInfo.swift @@ -58,8 +58,11 @@ struct UserSessionInfo: Identifiable { /// Last seen IP location let lastSeenIPLocation: String? - /// Device name - let deviceName: String? + /// Client name + let clientName: String? + + /// Client version + let clientVersion: String? /// True to indicate that session has been used under `inactiveSessionDurationTreshold` value let isActive: Bool diff --git a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift index 212434309..8fa03b02c 100644 --- a/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/View/UserSessionCardView.swift @@ -150,7 +150,8 @@ struct UserSessionCardViewPreview: View { deviceModel: nil, deviceOS: "iOS 15.5", lastSeenIPLocation: nil, - deviceName: "My iPhone", + clientName: "Element", + clientVersion: "1.0.0", isActive: true, isCurrent: isCurrent) viewData = UserSessionCardViewData(sessionInfo: sessionInfo) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift index 37233e141..90fa84a36 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift @@ -53,7 +53,8 @@ enum MockUserSessionDetailsScreenState: MockScreenState, CaseIterable { deviceModel: nil, deviceOS: "iOS 15.5", lastSeenIPLocation: nil, - deviceName: "My iPhone", + clientName: "Element", + clientVersion: "1.0.0", isActive: true, isCurrent: true) case .sessionSectionOnly: @@ -69,7 +70,8 @@ enum MockUserSessionDetailsScreenState: MockScreenState, CaseIterable { deviceModel: nil, deviceOS: "Android 4.0", lastSeenIPLocation: nil, - deviceName: "My Phone", + clientName: "Element", + clientVersion: "1.0.0", isActive: true, isCurrent: false) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift index 184725e1e..b07751c10 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/Unit/UserSessionDetailsViewModelTests.swift @@ -113,7 +113,8 @@ class UserSessionDetailsViewModelTests: XCTestCase { deviceModel: String? = nil, deviceOS: String? = nil, lastSeenIPLocation: String? = nil, - deviceName: String? = nil, + clientName: String? = nil, + clientVersion: String? = nil, isActive: Bool = true, isCurrent: Bool = true) -> UserSessionInfo { UserSessionInfo(id: id, @@ -128,7 +129,8 @@ class UserSessionDetailsViewModelTests: XCTestCase { deviceModel: deviceModel, deviceOS: deviceOS, lastSeenIPLocation: lastSeenIPLocation, - deviceName: deviceName, + clientName: clientName, + clientVersion: clientVersion, isActive: isActive, isCurrent: isCurrent) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift index 475f3bb5f..f08ecdd8e 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/UserSessionDetailsViewModel.swift @@ -93,6 +93,12 @@ class UserSessionDetailsViewModel: UserSessionDetailsViewModelType, UserSessionD deviceSectionItems.append(.init(title: VectorL10n.userSessionDetailsDeviceModel, value: model)) } + if session.deviceType == .web, + let clientName = session.clientName, + let clientVersion = session.clientVersion { + deviceSectionItems.append(.init(title: VectorL10n.userSessionDetailsDeviceBrowser, + value: "\(clientName) \(clientVersion)")) + } if let deviceOS = session.deviceOS { deviceSectionItems.append(.init(title: VectorL10n.userSessionDetailsDeviceOs, value: deviceOS)) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift index aa505c244..97c7ebc35 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift @@ -60,7 +60,8 @@ enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { deviceModel: nil, deviceOS: "iOS 15.5", lastSeenIPLocation: nil, - deviceName: "My iPhone", + clientName: "Element", + clientVersion: "1.0.0", isActive: true, isCurrent: true) service = MockUserSessionOverviewService() @@ -77,7 +78,8 @@ enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { deviceModel: nil, deviceOS: "macOS 12.5.1", lastSeenIPLocation: nil, - deviceName: "My Mac", + clientName: "Electron", + clientVersion: "20.1.1", isActive: false, isCurrent: false) service = MockUserSessionOverviewService() diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift index 416b0e032..a2d0e7807 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift @@ -93,13 +93,14 @@ class UserSessionOverviewViewModelTests: XCTestCase { isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: Date().timeIntervalSince1970 - 100, - applicationName: "Element", + applicationName: "Element iOS", applicationVersion: "1.9.7", applicationURL: nil, deviceModel: "iPhone XS", deviceOS: "iOS 15.5", lastSeenIPLocation: nil, - deviceName: "Mobile", + clientName: "Element", + clientVersion: "1.9.7", isActive: true, isCurrent: true) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProvider.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProvider.swift index 7ab1f5a04..a8d36cc4e 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProvider.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProvider.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,7 +44,7 @@ class UserSessionsDataProvider: UserSessionsDataProviderProtocol { session.crypto.device(withDeviceId: deviceId, ofUser: userId) } - func accountData(for eventType: String) -> [AnyHashable : Any]? { + func accountData(for eventType: String) -> [AnyHashable: Any]? { session.accountData.accountData(forEventType: eventType) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProviderProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProviderProtocol.swift index ca65e218e..e97310a40 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProviderProtocol.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsDataProviderProtocol.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift index a84cd7d44..273072ea7 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/MatrixSDK/UserSessionsOverviewService.swift @@ -143,7 +143,8 @@ extension UserSessionInfo { deviceModel: userAgent?.deviceModel, deviceOS: userAgent?.deviceOS, lastSeenIPLocation: nil, - deviceName: userAgent?.clientName, + clientName: userAgent?.clientName, + clientVersion: userAgent?.clientVersion, isActive: isActive, isCurrent: isCurrent) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift index 00d90238b..a37607786 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift @@ -89,7 +89,8 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { deviceModel: nil, deviceOS: "iOS 15.5", lastSeenIPLocation: nil, - deviceName: "My iPhone", + clientName: "Element", + clientVersion: "1.0.0", isActive: true, isCurrent: true) } @@ -107,7 +108,8 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { deviceModel: nil, deviceOS: "macOS 12.5.1", lastSeenIPLocation: nil, - deviceName: "My Mac", + clientName: "Electron", + clientVersion: "20.0.0", isActive: active, isCurrent: false), UserSessionInfo(id: "2 verified: \(verified) active: \(active)", @@ -122,7 +124,8 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { deviceModel: nil, deviceOS: "Windows 10", lastSeenIPLocation: nil, - deviceName: "My Windows", + clientName: "Firefox", + clientVersion: "39.0", isActive: active, isCurrent: false), UserSessionInfo(id: "3 verified: \(verified) active: \(active)", @@ -137,7 +140,8 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { deviceModel: nil, deviceOS: "Android 4.0", lastSeenIPLocation: nil, - deviceName: "My Phone", + clientName: "Element", + clientVersion: "1.0.0", isActive: active, isCurrent: false)] } diff --git a/RiotTests/UserAgentParserTests.swift b/RiotTests/UserAgentParserTests.swift index d68306d67..e268deda6 100644 --- a/RiotTests/UserAgentParserTests.swift +++ b/RiotTests/UserAgentParserTests.swift @@ -121,15 +121,15 @@ class UserAgentParserTests: XCTestCase { let expected = [ UserAgent(deviceType: .desktop, - deviceModel: "Electron", - deviceOS: "Macintosh", - clientName: nil, - clientVersion: nil), + deviceModel: nil, + deviceOS: "macOS 10.15.7", + clientName: "Electron", + clientVersion: "20.1.1"), UserAgent(deviceType: .desktop, - deviceModel: "Electron", + deviceModel: nil, deviceOS: "Windows NT 10.0", - clientName: nil, - clientVersion: nil) + clientName: "Electron", + clientVersion: "20.1.1") ] XCTAssertEqual(userAgents, expected) @@ -147,30 +147,30 @@ class UserAgentParserTests: XCTestCase { let expected = [ UserAgent(deviceType: .web, - deviceModel: "Chrome", - deviceOS: "Macintosh", - clientName: nil, - clientVersion: nil), + deviceModel: nil, + deviceOS: "macOS 10.15.7", + clientName: "Chrome", + clientVersion: "104.0.5112.102"), UserAgent(deviceType: .web, - deviceModel: "Chrome", + deviceModel: nil, deviceOS: "Windows NT 10.0", - clientName: nil, - clientVersion: nil), + clientName: "Chrome", + clientVersion: "104.0.5112.102"), UserAgent(deviceType: .web, - deviceModel: "Firefox", - deviceOS: "Macintosh", - clientName: nil, - clientVersion: nil), + deviceModel: nil, + deviceOS: "macOS 10.10", + clientName: "Firefox", + clientVersion: "39.0"), UserAgent(deviceType: .web, - deviceModel: "Safari", - deviceOS: "Macintosh", - clientName: nil, - clientVersion: nil), + deviceModel: nil, + deviceOS: "macOS 10.10.2", + clientName: "Safari", + clientVersion: "8.0.3"), UserAgent(deviceType: .web, - deviceModel: "Chrome", + deviceModel: nil, deviceOS: "Android 9", - clientName: nil, - clientVersion: nil) + clientName: "Chrome", + clientVersion: "69.0.3497.100") ] XCTAssertEqual(userAgents, expected) @@ -181,7 +181,8 @@ class UserAgentParserTests: XCTestCase { "Element (iPhone X; OS 15.2; 3.00)", "Element/1.9.9; iOS", "Element/1.9.7 Android", - "Element/1.9.9; iOS " + "some random string", + "Element/1.9.9; iOS ", ] let userAgents = uaStrings.map { UserAgentParser.parse($0) } @@ -189,6 +190,7 @@ class UserAgentParserTests: XCTestCase { .unknown, .unknown, .unknown, + .unknown, UserAgent(deviceType: .mobile, deviceModel: nil, deviceOS: nil, diff --git a/changelog.d/pr-6788.change b/changelog.d/pr-6788.change new file mode 100644 index 000000000..99f00337f --- /dev/null +++ b/changelog.d/pr-6788.change @@ -0,0 +1 @@ +User session details: Include browser version for web sessions (PSG-761). From 01182cdd68b983918ba832177682a3a81f5d0c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sveinn=20=C3=AD=20Felli?= Date: Mon, 3 Oct 2022 17:38:06 +0000 Subject: [PATCH 096/104] Translated using Weblate (Icelandic) Currently translated at 80.1% (1761 of 2198 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/is/ --- Riot/Assets/is.lproj/Vector.strings | 75 ++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 7 deletions(-) diff --git a/Riot/Assets/is.lproj/Vector.strings b/Riot/Assets/is.lproj/Vector.strings index c2c8af304..ad9ba1194 100644 --- a/Riot/Assets/is.lproj/Vector.strings +++ b/Riot/Assets/is.lproj/Vector.strings @@ -1279,7 +1279,7 @@ "settings_key_backup_info_signout_warning" = "Taktu öryggisafrit af dulritunarlyklunum áður en þú skráir þig út svo þeir tapist ekki."; "settings_key_backup_info_none" = "Dulritunarlyklarnir þínir eru ekki öryggisafritaðir úr þessari setu."; "settings_key_backup_info_checking" = "Athuga…"; -"settings_deactivate_my_account" = "Gera notandaaðganginn minn óvirkann"; +"settings_deactivate_my_account" = "Gera notandaaðganginn endanlega óvirkan"; "settings_add_3pid_invalid_password_message" = "Ógild auðkenni"; "settings_add_3pid_password_title_msidsn" = "Bæta við símanúmeri"; "settings_add_3pid_password_title_email" = "Bæta við tölvupóstfangi"; @@ -1462,7 +1462,7 @@ "auth_reset_password_error_not_found" = "Tölvupóstfangið þitt lítur ekki út fyrir að vera tengt við Matrix-auðkenni á þessum heimaþjóni."; "auth_login_single_sign_on" = "Skrá inn"; "onboarding_use_case_existing_server_button" = "Tengjast þjóni"; -"onboarding_use_case_skip_button" = "sleppt þessari spurningu"; +"onboarding_use_case_skip_button" = "Sleppa þessari spurningu"; "onboarding_use_case_community_messaging" = "Samfélög"; "onboarding_use_case_work_messaging" = "Teymi"; "onboarding_use_case_personal_messaging" = "Vinir og fjölskylda"; @@ -1714,7 +1714,7 @@ "contacts_address_book_permission_denied_alert_title" = "Tengiliðir óvirkir"; "directory_search_results_title" = "Vafra í niðurstöðum möppu"; /* The placeholder string contains onboarding_use_case_skip_button as a tappable action */ -"onboarding_use_case_not_sure_yet" = "Ekki ennþá viss? Þú getur %@"; +"onboarding_use_case_not_sure_yet" = "Ekki ennþá viss? %@"; "call_invite_expired" = "Boð í símtal er útrunnið"; "notification_settings_suppress_from_bots" = "Útiloka tilkynningar frá vélmennum"; "notification_settings_sender_hint" = "@notandi:lén.is"; @@ -1966,7 +1966,7 @@ "security_settings_crosssigning_complete_security" = "Algjört öryggi"; "settings_key_backup_info" = "Öryggi dulritaðra skilaboða er tryggt með enda-í-enda dulritun. Einungis þú og viðtakendurnir hafa dulritunarlyklana til að lesa slík skilaboð."; "settings_labs_enable_ringing_for_group_calls" = "Hringing fyrir hópsímtöl"; -"onboarding_use_case_message" = "Við munum hjálpa þér að tengjast."; +"onboarding_use_case_message" = "Við munum hjálpa þér að tengjast"; "callbar_only_single_active_group" = "Ýttu til að taka þátt í hópsímtalinu (%@)"; "call_video_with_user" = "Myndsímtal við %@"; "notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Þú gerðir skilaboð héðan í frá sýnileg fyrir alla meðlimi spjallrásarinnar síðan þeir skráðu sig."; @@ -1980,7 +1980,7 @@ "location_sharing_live_timer_selector_long" = "í 8 klukkustundir"; "location_sharing_live_timer_selector_medium" = "í 1 klukkustund"; "location_sharing_live_timer_selector_short" = "í 15 mínútur"; -"location_sharing_live_list_item_stop_sharing_action" = "Hætta deilingu"; +"location_sharing_live_list_item_stop_sharing_action" = "Stöðva"; "location_sharing_live_list_item_current_user_display_name" = "Þú"; "location_sharing_live_list_item_last_update_invalid" = "Síðasta uppfærsla óþekkt"; "location_sharing_live_list_item_last_update" = "Uppfært fyrir %@ síðan"; @@ -2091,8 +2091,8 @@ "threads_beta_title" = "Spjallþræðir"; "threads_notice_done" = "Náði því"; "onboarding_celebration_button" = "Hefjumst handa"; -"onboarding_celebration_message" = "Kjörstillingarnar þínar hafa verið vistaðar."; -"onboarding_celebration_title" = "Nú ertu tilbúin(n)!"; +"onboarding_celebration_message" = "Farðu hvenær sem er í stillingarnar til að breyta notandasniðinu þínu."; +"onboarding_celebration_title" = "Lítur vel út!"; "onboarding_avatar_accessibility_label" = "Auðkennismynd"; "onboarding_avatar_message" = "Þú getur breytt þessu hvenær sem er."; "onboarding_avatar_title" = "Bættu við auðkennismynd"; @@ -2116,3 +2116,64 @@ "joining" = "Gengur til liðs við spjallrás"; // String for App Store "store_short_description" = "Öruggt dreifvinnsluspjall/VoIP"; +"device_name_unknown" = "Óþekkt forrit (biðlari)"; + +// First item is client name and second item is session display name +"user_session_name" = "%@: %@"; +"user_session_learn_more" = "Kanna nánar"; +"user_session_view_details" = "Skoða nánar"; +"user_session_verify_action" = "Sannprófa setu"; +"user_session_unverified_short" = "Óstaðfest"; +"user_session_verified_short" = "Staðfest"; +"user_session_unverified" = "Óstaðfest seta"; +"user_session_verified" = "Staðfest seta"; + +// MARK: User sessions management + +"user_sessions_overview_title" = "Setur"; +"space_selector_create_space" = "Búa til svæði"; +"all_chats_onboarding_try_it" = "Prófaðu það"; +"all_chats_edit_menu_space_settings" = "Stillingar svæðis"; +"all_chats_edit_menu_leave_space" = "Yfirgefa %@"; +"room_recents_recently_viewed_section" = "Nýlega skoðað"; +"all_chats_nothing_found_placeholder_title" = "Ekkert fannst."; +"all_chats_empty_list_placeholder_title" = "Þú hefur klárað allt."; +"all_chats_all_filter" = "Allt"; +"all_chats_edit_layout_show_filters" = "Sýna síur"; +"all_chats_edit_layout_show_recents" = "Birta nýlegt"; +"all_chats_edit_layout_unreads" = "Ólesið"; +"all_chats_edit_layout_recents" = "Nýlegt"; + +// Mark: Leave space + +"leave_space_action" = "Yfirgefa svæði"; +"spaces_creation_email_invites_title" = "Bjóddu félögum þínum"; +"spaces_creation_empty_room_name_error" = "Nafn er nauðsynlegt"; +"spaces_explore_rooms_format" = "Kanna %@"; +"room_info_back_button_title" = "Upplýsingar um spjallrás"; +"secrets_setup_recovery_passphrase_information" = "Settu inn öryggisfrasa sem aðeins þú þekkir, þetta er notað til að verja leyndarmálin sem þú geymir á netþjóninum þínum."; +"room_access_settings_screen_title" = "Hver hefur aðgang að þessari spjallrás?"; +"settings_presence_offline_mode" = "Ónettengdur hamur"; +"room_accessibility_record_voice_message" = "Taka upp talskilaboð"; +"authentication_recaptcha_title" = "Ertu mannvera?"; +"authentication_verify_msisdn_invalid_phone_number" = "Ógilt símanúmer"; +"authentication_verify_msisdn_waiting_button" = "Endursenda kóða"; +"authentication_verify_msisdn_text_field_placeholder" = "Símanúmer"; +"authentication_verify_msisdn_input_title" = "Settu inn símanúmerið þitt"; +"authentication_choose_password_not_verified_message" = "Athugaðu pósthólfið þitt"; +"authentication_choose_password_submit_button" = "Endurstilla lykilorð"; +"authentication_choose_password_text_field_placeholder" = "Nýtt lykilorð"; +"authentication_choose_password_input_title" = "Veldu nýtt lykilorð"; +"authentication_forgot_password_waiting_button" = "Senda tölvupóst aftur"; +"authentication_forgot_password_waiting_title" = "Athugaðu tölvupóstinn þinn."; +"authentication_forgot_password_text_field_placeholder" = "Tölvupóstur"; +"authentication_forgot_password_input_title" = "Settu inn tölvupóstfangið þitt"; +"authentication_verify_email_waiting_button" = "Senda tölvupóst aftur"; +"authentication_verify_email_waiting_title" = "Skoðaðu tölvupóstinn þinn."; +"authentication_verify_email_text_field_placeholder" = "Tölvupóstfang"; +"authentication_verify_email_input_title" = "Settu inn tölvupóstfangið þitt"; +"authentication_server_selection_server_url" = "Slóð á heimaþjón"; +"authentication_login_forgot_password" = "Gleymt lykilorð"; +"authentication_login_username" = "Notandanafn / tölvupóstfang / símanúmer"; +"authentication_login_title" = "Velkomin(n) aftur!"; +"authentication_registration_username" = "Notandanafn"; From d4f0a7a5e5652e5e67c12b411fada5c5383f3c4b Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Mon, 3 Oct 2022 04:58:52 +0000 Subject: [PATCH 097/104] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2198 of 2198 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ --- Riot/Assets/pt_BR.lproj/Vector.strings | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Riot/Assets/pt_BR.lproj/Vector.strings b/Riot/Assets/pt_BR.lproj/Vector.strings index 52be5eaa8..362dd9620 100644 --- a/Riot/Assets/pt_BR.lproj/Vector.strings +++ b/Riot/Assets/pt_BR.lproj/Vector.strings @@ -2230,7 +2230,7 @@ "settings_labs_enable_auto_report_decryption_errors" = "Auto Reportar Erros de Decriptação"; "room_preview_decline_invitation_options" = "Você quer declinar o convite ou ignorar esta(e) usuária(o)?"; "threads_beta_cancel" = "Não agora"; -"threads_beta_enable" = "Teste aí"; +"threads_beta_enable" = "Experimentar"; "threads_beta_information_link" = "Saber mais"; "threads_beta_information" = "Mantenha discussões organizadas com threads.\n\nThreads ajudam manter suas conversas em-tópico e fáceis de rastrear. "; "threads_beta_title" = "Threads"; @@ -2446,12 +2446,12 @@ // Mark: - Room invites "room_invites_empty_view_title" = "Nada novo."; -"all_chats_onboarding_try_it" = "Testa aí"; +"all_chats_onboarding_try_it" = "Experimentar"; "all_chats_onboarding_title" = "O que tem de novo"; "all_chats_onboarding_page_message3" = "Toque em seu perfil para nos deixar sabendo do que você acha."; -"all_chats_onboarding_page_title3" = "Dar Feedback"; -"all_chats_onboarding_page_message2" = "Acesse seus Espaços (esquerdo fundo) mais rápido e fácil que jamais antes."; -"all_chats_onboarding_page_title2" = "Acessar Espaços"; +"all_chats_onboarding_page_title3" = "Dê Feedback"; +"all_chats_onboarding_page_message2" = "Acesse seus Espaços (esquerda fundo) mais rápido e fácil que jamais antes."; +"all_chats_onboarding_page_title2" = "Acesse Espaços"; "all_chats_onboarding_page_message1" = "Para simplificar seu Element, abas são agora opcionais. Gerencie-as usando o menu direito topo."; "all_chats_onboarding_page_title1" = "Boas vindas a uma nova visão!"; "all_chats_nothing_found_placeholder_message" = "Tente ajustar sua pesquisa."; From 5cb7a1ea2057885d135f4405762abb040e060c25 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 4 Oct 2022 09:52:01 +0100 Subject: [PATCH 098/104] Fix failure building tests. --- .../MockUserSessionOverviewScreenState.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift index 97c7ebc35..ebf382ad7 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift @@ -96,7 +96,8 @@ enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { deviceModel: nil, deviceOS: "macOS 12.5.1", lastSeenIPLocation: nil, - deviceName: "My Mac", + clientName: "My Mac", + clientVersion: "1.0.0", isActive: false, isCurrent: false) service = MockUserSessionOverviewService(pusherEnabled: enabled) @@ -113,7 +114,8 @@ enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { deviceModel: nil, deviceOS: "macOS 12.5.1", lastSeenIPLocation: nil, - deviceName: "My Mac", + clientName: "My Mac", + clientVersion: "1.0.0", isActive: false, isCurrent: false) service = MockUserSessionOverviewService(pusherEnabled: true, remotelyTogglingPushersAvailable: false) From 18ccea6fdc0abe3fcfd4c9e7bd25baab2a554d22 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 4 Oct 2022 12:20:11 +0100 Subject: [PATCH 099/104] Fix incorrect placeholder type. --- Riot/Assets/de.lproj/Vector.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 3d8567824..1942aa352 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -2220,7 +2220,7 @@ "authentication_forgot_password_waiting_button" = "E-Mail erneut senden"; "authentication_verify_email_input_title" = "Gib deine E-Mail-Adresse ein"; /* The placeholder will show the full Matrix ID that has been entered. */ -"authentication_registration_username_footer_available" = "Andere können dich als %s finden"; +"authentication_registration_username_footer_available" = "Andere können dich als %@ finden"; "authentication_registration_username" = "Nutzername"; "onboarding_celebration_message" = "Dein kannst dein Profil jederzeit in den Einstellungen ändern"; "authentication_registration_password_footer" = "8 Zeichen oder mehr"; From fb9cf110d85d330dfc08f3cabab74b66daf4251e Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 4 Oct 2022 12:22:49 +0100 Subject: [PATCH 100/104] Remove duplicate strings. --- Riot/Assets/en.lproj/Vector.strings | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index cb141e966..571cf8959 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2105,10 +2105,7 @@ Tap the + to start adding people."; "spaces_creation_in_many_spaces" = "in %@ spaces"; "spaces_creation_in_one_space" = "in 1 space"; -"spaces_invite_people" = "Invite people"; -"spaces_add_room" = "Add room"; "spaces_add_room_missing_permission_message" = "You do not have permissions to add rooms to this space."; -"spaces_add_space" = "Add space"; // Mark: Leave space @@ -2728,12 +2725,9 @@ To enable access, tap Settings> Location and select Always"; "user_id_title" = "User ID:"; "offline" = "offline"; "unsent" = "Unsent"; -"error" = "Error"; "error_common_message" = "An error occured. Please try again later."; "not_supported_yet" = "Not supported yet"; "default" = "default"; -"private" = "Private"; -"public" = "Public"; "power_level" = "Power Level"; "network_error_not_reachable" = "Please check your network connectivity"; "user_id_placeholder" = "ex: @bob:homeserver"; From 4ca8f2703f47703c6ea9fa54041377627627930c Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 4 Oct 2022 13:00:09 +0100 Subject: [PATCH 101/104] changelog.d: Upgrade MatrixSDK version ([v0.24.0](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.24.0)). --- Podfile | 2 +- changelog.d/x-nolink-0.change | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/x-nolink-0.change diff --git a/Podfile b/Podfile index ff30fcb7b..e74068a7e 100644 --- a/Podfile +++ b/Podfile @@ -16,7 +16,7 @@ use_frameworks! # - `{ :specHash => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for MatrixSDK repo. Used by Fastfile during CI # # Warning: our internal tooling depends on the name of this variable name, so be sure not to change it -$matrixSDKVersion = '= 0.23.19' +$matrixSDKVersion = '= 0.24.0' # $matrixSDKVersion = :local # $matrixSDKVersion = { :branch => 'develop'} # $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } } diff --git a/changelog.d/x-nolink-0.change b/changelog.d/x-nolink-0.change new file mode 100644 index 000000000..b3dc15eeb --- /dev/null +++ b/changelog.d/x-nolink-0.change @@ -0,0 +1 @@ +Upgrade MatrixSDK version ([v0.24.0](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.24.0)). \ No newline at end of file From fc0ee8c00bc957cba501ea1868f42fe49ea09d87 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 4 Oct 2022 13:00:09 +0100 Subject: [PATCH 102/104] version++ --- CHANGES.md | 33 +++++++++++++++++++++++++++++++++ changelog.d/6580.bugfix | 1 - changelog.d/6693.wip | 1 - changelog.d/6717.bugfix | 1 - changelog.d/6723.bugfix | 1 - changelog.d/6742.change | 1 - changelog.d/6748.bugfix | 1 - changelog.d/6749.bugfix | 1 - changelog.d/6753.wip | 1 - changelog.d/6755.bugfix | 1 - changelog.d/6770.wip | 1 - changelog.d/6778.bugfix | 1 - changelog.d/6781.change | 1 - changelog.d/6787.change | 1 - changelog.d/pr-6757.build | 1 - changelog.d/pr-6766.change | 1 - changelog.d/pr-6780.change | 1 - changelog.d/pr-6788.change | 1 - changelog.d/x-nolink-0.change | 1 - 19 files changed, 33 insertions(+), 18 deletions(-) delete mode 100644 changelog.d/6580.bugfix delete mode 100644 changelog.d/6693.wip delete mode 100644 changelog.d/6717.bugfix delete mode 100644 changelog.d/6723.bugfix delete mode 100644 changelog.d/6742.change delete mode 100644 changelog.d/6748.bugfix delete mode 100644 changelog.d/6749.bugfix delete mode 100644 changelog.d/6753.wip delete mode 100644 changelog.d/6755.bugfix delete mode 100644 changelog.d/6770.wip delete mode 100644 changelog.d/6778.bugfix delete mode 100644 changelog.d/6781.change delete mode 100644 changelog.d/6787.change delete mode 100644 changelog.d/pr-6757.build delete mode 100644 changelog.d/pr-6766.change delete mode 100644 changelog.d/pr-6780.change delete mode 100644 changelog.d/pr-6788.change delete mode 100644 changelog.d/x-nolink-0.change diff --git a/CHANGES.md b/CHANGES.md index 9e42b90fc..427842bc0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,36 @@ +## Changes in 1.9.8 (2022-10-04) + +🙌 Improvements + +- UserSessions: Extended device information (PSG-772). ([#6766](https://github.com/vector-im/element-ios/pull/6766)) +- Settings: Add labs flags for new session manager (PSG-792, PSG-799). ([#6780](https://github.com/vector-im/element-ios/pull/6780)) +- User session details: Include browser version for web sessions (PSG-761). ([#6788](https://github.com/vector-im/element-ios/pull/6788)) +- Upgrade MatrixSDK version ([v0.24.0](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.24.0)). +- Include concrete device model in user agent (PSG-754) ([#6742](https://github.com/vector-im/element-ios/issues/6742)) +- CryptoV2: Manual device verification ([#6781](https://github.com/vector-im/element-ios/issues/6781)) +- User sessions: Add support for MSC3881 ([#6787](https://github.com/vector-im/element-ios/issues/6787)) + +🐛 Bugfixes + +- Voiceover: Add labels to User Menu and My Spaces buttons on the All Chats view. ([#6580](https://github.com/vector-im/element-ios/issues/6580)) +- Media: Avoid redundant reloads on media viewer. ([#6717](https://github.com/vector-im/element-ios/issues/6717)) +- Pass country code into MSISDN requests ([#6723](https://github.com/vector-im/element-ios/issues/6723)) +- Fix render of links with both characters requiring percent encoding and markdown-like syntax ([#6748](https://github.com/vector-im/element-ios/issues/6748)) +- Fix crash when scrolling chat list ([#6749](https://github.com/vector-im/element-ios/issues/6749)) +- App Layout: Unable to send message after filtering for room ([#6755](https://github.com/vector-im/element-ios/issues/6755)) +- Fix code block background colour ([#6778](https://github.com/vector-im/element-ios/issues/6778)) + +🧱 Build + +- Fix crash when scrolling chat list ([#6757](https://github.com/vector-im/element-ios/pull/6757)) + +🚧 In development 🚧 + +- Device manager: User session details screen. ([#6693](https://github.com/vector-im/element-ios/issues/6693)) +- Device manager: Session overview screen. ([#6753](https://github.com/vector-im/element-ios/issues/6753)) +- Device Manager: Link current session to session overview. ([#6770](https://github.com/vector-im/element-ios/issues/6770)) + + ## Changes in 1.9.7 (2022-09-28) 🙌 Improvements diff --git a/changelog.d/6580.bugfix b/changelog.d/6580.bugfix deleted file mode 100644 index 1ca94a1df..000000000 --- a/changelog.d/6580.bugfix +++ /dev/null @@ -1 +0,0 @@ -Voiceover: Add labels to User Menu and My Spaces buttons on the All Chats view. diff --git a/changelog.d/6693.wip b/changelog.d/6693.wip deleted file mode 100644 index a50a16691..000000000 --- a/changelog.d/6693.wip +++ /dev/null @@ -1 +0,0 @@ -Device manager: User session details screen. diff --git a/changelog.d/6717.bugfix b/changelog.d/6717.bugfix deleted file mode 100644 index c3cc45ae3..000000000 --- a/changelog.d/6717.bugfix +++ /dev/null @@ -1 +0,0 @@ -Media: Avoid redundant reloads on media viewer. diff --git a/changelog.d/6723.bugfix b/changelog.d/6723.bugfix deleted file mode 100644 index 228ff2f47..000000000 --- a/changelog.d/6723.bugfix +++ /dev/null @@ -1 +0,0 @@ -Pass country code into MSISDN requests diff --git a/changelog.d/6742.change b/changelog.d/6742.change deleted file mode 100644 index 38aab166a..000000000 --- a/changelog.d/6742.change +++ /dev/null @@ -1 +0,0 @@ -Include concrete device model in user agent (PSG-754) diff --git a/changelog.d/6748.bugfix b/changelog.d/6748.bugfix deleted file mode 100644 index 0c9d08465..000000000 --- a/changelog.d/6748.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix render of links with both characters requiring percent encoding and markdown-like syntax diff --git a/changelog.d/6749.bugfix b/changelog.d/6749.bugfix deleted file mode 100644 index e84ee6f0c..000000000 --- a/changelog.d/6749.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix crash when scrolling chat list diff --git a/changelog.d/6753.wip b/changelog.d/6753.wip deleted file mode 100644 index a96560d05..000000000 --- a/changelog.d/6753.wip +++ /dev/null @@ -1 +0,0 @@ -Device manager: Session overview screen. diff --git a/changelog.d/6755.bugfix b/changelog.d/6755.bugfix deleted file mode 100644 index 3e1d8ee1d..000000000 --- a/changelog.d/6755.bugfix +++ /dev/null @@ -1 +0,0 @@ -App Layout: Unable to send message after filtering for room \ No newline at end of file diff --git a/changelog.d/6770.wip b/changelog.d/6770.wip deleted file mode 100644 index 89911c57c..000000000 --- a/changelog.d/6770.wip +++ /dev/null @@ -1 +0,0 @@ -Device Manager: Link current session to session overview. diff --git a/changelog.d/6778.bugfix b/changelog.d/6778.bugfix deleted file mode 100644 index 9721d8cff..000000000 --- a/changelog.d/6778.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix code block background colour diff --git a/changelog.d/6781.change b/changelog.d/6781.change deleted file mode 100644 index 7ff78b730..000000000 --- a/changelog.d/6781.change +++ /dev/null @@ -1 +0,0 @@ -CryptoV2: Manual device verification diff --git a/changelog.d/6787.change b/changelog.d/6787.change deleted file mode 100644 index b4defa9d7..000000000 --- a/changelog.d/6787.change +++ /dev/null @@ -1 +0,0 @@ -User sessions: Add support for MSC3881 diff --git a/changelog.d/pr-6757.build b/changelog.d/pr-6757.build deleted file mode 100644 index e84ee6f0c..000000000 --- a/changelog.d/pr-6757.build +++ /dev/null @@ -1 +0,0 @@ -Fix crash when scrolling chat list diff --git a/changelog.d/pr-6766.change b/changelog.d/pr-6766.change deleted file mode 100644 index 3f5ff2693..000000000 --- a/changelog.d/pr-6766.change +++ /dev/null @@ -1 +0,0 @@ -UserSessions: Extended device information (PSG-772). diff --git a/changelog.d/pr-6780.change b/changelog.d/pr-6780.change deleted file mode 100644 index a50689634..000000000 --- a/changelog.d/pr-6780.change +++ /dev/null @@ -1 +0,0 @@ -Settings: Add labs flags for new session manager (PSG-792, PSG-799). diff --git a/changelog.d/pr-6788.change b/changelog.d/pr-6788.change deleted file mode 100644 index 99f00337f..000000000 --- a/changelog.d/pr-6788.change +++ /dev/null @@ -1 +0,0 @@ -User session details: Include browser version for web sessions (PSG-761). diff --git a/changelog.d/x-nolink-0.change b/changelog.d/x-nolink-0.change deleted file mode 100644 index b3dc15eeb..000000000 --- a/changelog.d/x-nolink-0.change +++ /dev/null @@ -1 +0,0 @@ -Upgrade MatrixSDK version ([v0.24.0](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.24.0)). \ No newline at end of file From 1fb580edeaf330c7f34edf75d94582de7b4190d9 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 4 Oct 2022 14:55:51 +0100 Subject: [PATCH 103/104] Fix missing toolbar in AllChatsViewController. The empty artwork view was obscuring it. --- CHANGES.md | 1 + Riot/Modules/Common/Recents/RecentsViewController.h | 6 ++++++ Riot/Modules/Common/Recents/RecentsViewController.m | 3 ++- Riot/Modules/Home/AllChats/AllChatsViewController.swift | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 427842bc0..db410ce33 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,6 +18,7 @@ - Fix render of links with both characters requiring percent encoding and markdown-like syntax ([#6748](https://github.com/vector-im/element-ios/issues/6748)) - Fix crash when scrolling chat list ([#6749](https://github.com/vector-im/element-ios/issues/6749)) - App Layout: Unable to send message after filtering for room ([#6755](https://github.com/vector-im/element-ios/issues/6755)) +- App Layout: Fix missing toolbar for users with no rooms. ([#6796](https://github.com/vector-im/element-ios/pull/6796)) - Fix code block background colour ([#6778](https://github.com/vector-im/element-ios/issues/6778)) 🧱 Build diff --git a/Riot/Modules/Common/Recents/RecentsViewController.h b/Riot/Modules/Common/Recents/RecentsViewController.h index 09a9ee572..9a9c60ece 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.h +++ b/Riot/Modules/Common/Recents/RecentsViewController.h @@ -93,6 +93,12 @@ FOUNDATION_EXPORT NSString *const RecentsViewControllerDataReadyNotification; */ @property (nonatomic, weak) RootTabEmptyView *emptyView; +/** + The bottom anchor used to layout `emptyView` in the absence of a FAB. + If this value is `nil` the empty view will be anchored to the bottom of its superview. + */ +@property (nonatomic, weak) NSLayoutYAxisAnchor *emptyViewBottomAnchor; + /** The screen timer used for analytics if they've been enabled. The default value is nil. */ diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m index c06a6f18a..0a86bd3c0 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -2226,7 +2226,8 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro [self.view addSubview:emptyView]; } - emptyViewBottomConstraint = [emptyView.bottomAnchor constraintEqualToAnchor:emptyView.superview.bottomAnchor]; + NSLayoutYAxisAnchor *bottomAnchor = self.emptyViewBottomAnchor ?: emptyView.superview.bottomAnchor; + emptyViewBottomConstraint = [emptyView.bottomAnchor constraintEqualToAnchor:bottomAnchor constant:-1]; // 1pt spacing for UIToolbar's divider. emptyView.translatesAutoresizingMaskIntoConstraints = NO; diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index b2cbc8ce8..1b26bfe81 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -127,6 +127,7 @@ class AllChatsViewController: HomeViewController { recentsTableView.contentInsetAdjustmentBehavior = .automatic toolbarHeight = toolbar.frame.height + emptyViewBottomAnchor = toolbar.topAnchor updateUI() From ce3810ecadf15fd8ca778f7bbfbe22a4bce49a58 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 4 Oct 2022 15:54:57 +0100 Subject: [PATCH 104/104] Fix navigation bars inside of VectorHostingController. --- CHANGES.md | 1 + .../Common/SwiftUI/VectorHostingController.swift | 16 ++++++++-------- .../Coordinator/AnalyticsPromptCoordinator.swift | 3 ++- .../OnboardingCelebrationCoordinator.swift | 1 + .../OnboardingCongratulationsCoordinator.swift | 1 + .../OnboardingSplashScreenCoordinator.swift | 1 + 6 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 427842bc0..7091d0f36 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,7 @@ - Fix crash when scrolling chat list ([#6749](https://github.com/vector-im/element-ios/issues/6749)) - App Layout: Unable to send message after filtering for room ([#6755](https://github.com/vector-im/element-ios/issues/6755)) - Fix code block background colour ([#6778](https://github.com/vector-im/element-ios/issues/6778)) +- Fix navigation bars visibility on iOS 16. ([#6799](https://github.com/vector-im/element-ios/pull/6799)) 🧱 Build diff --git a/Riot/Modules/Common/SwiftUI/VectorHostingController.swift b/Riot/Modules/Common/SwiftUI/VectorHostingController.swift index 9b6860fb7..98faba5c0 100644 --- a/Riot/Modules/Common/SwiftUI/VectorHostingController.swift +++ b/Riot/Modules/Common/SwiftUI/VectorHostingController.swift @@ -73,14 +73,6 @@ class VectorHostingController: UIHostingController { bottomSheetPreferences?.setup(viewController: self) } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - if isNavigationBarHidden { - self.navigationController?.isNavigationBarHidden = true - } - } - override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) @@ -93,6 +85,14 @@ class VectorHostingController: UIHostingController { } } + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + + if let navigationController = navigationController, navigationController.isNavigationBarHidden != isNavigationBarHidden { + navigationController.isNavigationBarHidden = isNavigationBarHidden + } + } + override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() diff --git a/RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptCoordinator.swift b/RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptCoordinator.swift index fb79b8df8..b4d9daf80 100644 --- a/RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptCoordinator.swift +++ b/RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptCoordinator.swift @@ -27,7 +27,7 @@ final class AnalyticsPromptCoordinator: Coordinator, Presentable { // MARK: Private private let parameters: AnalyticsPromptCoordinatorParameters - private let analyticsPromptHostingController: UIViewController + private let analyticsPromptHostingController: VectorHostingController private var _analyticsPromptViewModel: Any? fileprivate var analyticsPromptViewModel: AnalyticsPromptViewModel { @@ -59,6 +59,7 @@ final class AnalyticsPromptCoordinator: Coordinator, Presentable { let view = AnalyticsPrompt(viewModel: viewModel.context) _analyticsPromptViewModel = viewModel analyticsPromptHostingController = VectorHostingController(rootView: view) + analyticsPromptHostingController.isNavigationBarHidden = true } // MARK: - Public diff --git a/RiotSwiftUI/Modules/Onboarding/Celebration/Coordinator/OnboardingCelebrationCoordinator.swift b/RiotSwiftUI/Modules/Onboarding/Celebration/Coordinator/OnboardingCelebrationCoordinator.swift index 9f0ea9273..9db9439df 100644 --- a/RiotSwiftUI/Modules/Onboarding/Celebration/Coordinator/OnboardingCelebrationCoordinator.swift +++ b/RiotSwiftUI/Modules/Onboarding/Celebration/Coordinator/OnboardingCelebrationCoordinator.swift @@ -45,6 +45,7 @@ final class OnboardingCelebrationCoordinator: Coordinator, Presentable { onboardingCelebrationViewModel = viewModel onboardingCelebrationHostingController = VectorHostingController(rootView: view) onboardingCelebrationHostingController.enableNavigationBarScrollEdgeAppearance = true + onboardingCelebrationHostingController.isNavigationBarHidden = true } // MARK: - Public diff --git a/RiotSwiftUI/Modules/Onboarding/Congratulations/Coordinator/OnboardingCongratulationsCoordinator.swift b/RiotSwiftUI/Modules/Onboarding/Congratulations/Coordinator/OnboardingCongratulationsCoordinator.swift index 57cd09d5c..d2f23340e 100644 --- a/RiotSwiftUI/Modules/Onboarding/Congratulations/Coordinator/OnboardingCongratulationsCoordinator.swift +++ b/RiotSwiftUI/Modules/Onboarding/Congratulations/Coordinator/OnboardingCongratulationsCoordinator.swift @@ -58,6 +58,7 @@ final class OnboardingCongratulationsCoordinator: Coordinator, Presentable { onboardingCongratulationsViewModel = viewModel onboardingCongratulationsHostingController = VectorHostingController(rootView: view) onboardingCongratulationsHostingController.statusBarStyle = .lightContent + onboardingCongratulationsHostingController.isNavigationBarHidden = true } // MARK: - Public diff --git a/RiotSwiftUI/Modules/Onboarding/SplashScreen/Coordinator/OnboardingSplashScreenCoordinator.swift b/RiotSwiftUI/Modules/Onboarding/SplashScreen/Coordinator/OnboardingSplashScreenCoordinator.swift index 27c4b3dbc..669ac19cb 100644 --- a/RiotSwiftUI/Modules/Onboarding/SplashScreen/Coordinator/OnboardingSplashScreenCoordinator.swift +++ b/RiotSwiftUI/Modules/Onboarding/SplashScreen/Coordinator/OnboardingSplashScreenCoordinator.swift @@ -46,6 +46,7 @@ final class OnboardingSplashScreenCoordinator: OnboardingSplashScreenCoordinator onboardingSplashScreenViewModel = viewModel onboardingSplashScreenHostingController = VectorHostingController(rootView: view) onboardingSplashScreenHostingController.vc_removeBackTitle() + onboardingSplashScreenHostingController.isNavigationBarHidden = true indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: onboardingSplashScreenHostingController) }