From 9ff69eaaf000fa780f23bbc40c53c156b81fe284 Mon Sep 17 00:00:00 2001 From: David Langley Date: Thu, 12 Aug 2021 11:52:06 +0100 Subject: [PATCH] Add theming support, and support for other top level configuration to swiftui views with VectorHostingViewController, VectorContentModifier. Add VisibilityModifier. Move from List to VStack. --- DesignKit/Source/AvatarSize.swift | 35 ++++++ Riot/Categories/Combine/Publisher.swift | 35 ++++++ Riot/Managers/Theme/SwiftUI/ThemeKey.swift | 37 ++++++ .../Theme/{ => SwiftUI}/ThemeObserver.swift | 20 ++-- Riot/Managers/Theme/ThemeIdentifier.swift | 1 - Riot/Managers/Theme/ThemeService.swift | 20 ---- .../Common/Avatar/AvatarInputType.swift | 34 ++++++ .../Modules/Common/Avatar/AvatarService.swift | 108 ++++++++++++++++++ .../Common/Avatar/MockAvatarService.swift | 30 +++++ .../Common/SwiftUI/VectorContentView.swift | 39 +++++++ .../SwiftUI/VectorHostingViewController.swift | 64 +++++++++++ .../Common/SwiftUI/VisibilityModifier.swift | 57 +++++++++ .../RoomNotificationSettingsCoordinator.swift | 41 ++++--- .../RoomNotificationSettingsViewModel.swift | 39 +++++-- ...oomNotificationSettingsViewStateType.swift | 10 +- .../RoomNotificationState.swift | 8 +- .../SwiftUI/FormPickerItem.swift | 59 ---------- .../SwiftUI/FormPickerItemView.swift | 66 +++++++++++ ...oter.swift => FormSectionFooterView.swift} | 20 ++-- ...ader.swift => FormSectionHeaderView.swift} | 22 ++-- .../RoomNotificationSettingsHeaderView.swift | 50 ++++++++ .../RoomNotificationSettingsView.swift | 53 +++++---- .../SwiftUI/VectorAvatarView.swift | 44 +++++++ .../SwiftUI/VectorForm.swift | 47 -------- .../SwiftUI/VectorFormItemButtonStyle.swift | 29 +++++ .../SwiftUI/VectorFormView.swift | 63 ++++++++++ ...omNotificationSettingsViewController.swift | 2 +- .../Room/RoomInfo/RoomInfoCoordinator.swift | 2 +- .../RoomInfoList/Views/RoomInfoBasicView.xib | 19 ++- 29 files changed, 841 insertions(+), 213 deletions(-) create mode 100644 DesignKit/Source/AvatarSize.swift create mode 100644 Riot/Categories/Combine/Publisher.swift create mode 100644 Riot/Managers/Theme/SwiftUI/ThemeKey.swift rename Riot/Managers/Theme/{ => SwiftUI}/ThemeObserver.swift (55%) create mode 100644 Riot/Modules/Common/Avatar/AvatarInputType.swift create mode 100644 Riot/Modules/Common/Avatar/AvatarService.swift create mode 100644 Riot/Modules/Common/Avatar/MockAvatarService.swift create mode 100644 Riot/Modules/Common/SwiftUI/VectorContentView.swift create mode 100644 Riot/Modules/Common/SwiftUI/VectorHostingViewController.swift create mode 100644 Riot/Modules/Common/SwiftUI/VisibilityModifier.swift delete mode 100644 Riot/Modules/Room/NotificationSettings/SwiftUI/FormPickerItem.swift create mode 100644 Riot/Modules/Room/NotificationSettings/SwiftUI/FormPickerItemView.swift rename Riot/Modules/Room/NotificationSettings/SwiftUI/{FormSectionFooter.swift => FormSectionFooterView.swift} (63%) rename Riot/Modules/Room/NotificationSettings/SwiftUI/{FormSectionHeader.swift => FormSectionHeaderView.swift} (63%) create mode 100644 Riot/Modules/Room/NotificationSettings/SwiftUI/RoomNotificationSettingsHeaderView.swift create mode 100644 Riot/Modules/Room/NotificationSettings/SwiftUI/VectorAvatarView.swift delete mode 100644 Riot/Modules/Room/NotificationSettings/SwiftUI/VectorForm.swift create mode 100644 Riot/Modules/Room/NotificationSettings/SwiftUI/VectorFormItemButtonStyle.swift create mode 100644 Riot/Modules/Room/NotificationSettings/SwiftUI/VectorFormView.swift diff --git a/DesignKit/Source/AvatarSize.swift b/DesignKit/Source/AvatarSize.swift new file mode 100644 index 000000000..bac46e6f3 --- /dev/null +++ b/DesignKit/Source/AvatarSize.swift @@ -0,0 +1,35 @@ +// +// 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 + +// Figma Avatar Sizes: https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=1258%3A19678 +public enum AvatarSize: Int { + case xxSmall = 16 + case xSmall = 32 + case small = 36 + case medium = 42 + case large = 44 + case xLarge = 52 + case xxLarge = 80 +} + +extension AvatarSize { + public var size: CGSize { + return CGSize(width: self.rawValue, height: self.rawValue) + } +} diff --git a/Riot/Categories/Combine/Publisher.swift b/Riot/Categories/Combine/Publisher.swift new file mode 100644 index 000000000..6bc37c9b2 --- /dev/null +++ b/Riot/Categories/Combine/Publisher.swift @@ -0,0 +1,35 @@ +// +// 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 Combine + +/** + Sams 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.) + - SeeAlso: + [assign(to:on:)](https://developer.apple.com/documentation/combine/just/assign(to:on:)) + */ +@available(iOS 14.0, *) +extension Publisher where Failure == Never { + func weakAssign( + to keyPath: ReferenceWritableKeyPath, + on object: T + ) -> AnyCancellable { + sink { [weak object] value in + object?[keyPath: keyPath] = value + } + } +} diff --git a/Riot/Managers/Theme/SwiftUI/ThemeKey.swift b/Riot/Managers/Theme/SwiftUI/ThemeKey.swift new file mode 100644 index 000000000..e201170ea --- /dev/null +++ b/Riot/Managers/Theme/SwiftUI/ThemeKey.swift @@ -0,0 +1,37 @@ +// +// 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 + +private struct ThemeKey: EnvironmentKey { + static let defaultValue = ThemeService.shared().theme +} + +@available(iOS 14.0, *) +extension EnvironmentValues { + var theme: Theme { + get { self[ThemeKey.self] } + set { self[ThemeKey.self] = newValue } + } +} + +@available(iOS 14.0, *) +extension View { + func theme(_ theme: Theme) -> some View { + environment(\.theme, theme) + } +} diff --git a/Riot/Managers/Theme/ThemeObserver.swift b/Riot/Managers/Theme/SwiftUI/ThemeObserver.swift similarity index 55% rename from Riot/Managers/Theme/ThemeObserver.swift rename to Riot/Managers/Theme/SwiftUI/ThemeObserver.swift index 212626800..bc985581c 100644 --- a/Riot/Managers/Theme/ThemeObserver.swift +++ b/Riot/Managers/Theme/SwiftUI/ThemeObserver.swift @@ -17,21 +17,15 @@ import Foundation import Combine -@available(iOS 13.0, *) -class ThemeServiceObserver: ObservableObject { +@available(iOS 14.0, *) +class ThemeObserver: ObservableObject { - static let shared = ThemeServiceObserver() - - var cancelable: Cancellable? + static let shared = ThemeObserver() init() { - let themePubliser = NotificationCenter.default.publisher(for: NSNotification.Name.themeServiceDidChangeTheme).map { _ in - ThemeService.shared().themeIdentifier - } - cancelable = themePubliser.sink { [weak self] id in - guard let self = self else { return } - self.themeId = id - } + NotificationCenter.default.publisher(for: NSNotification.Name.themeServiceDidChangeTheme).map { _ in + ThemeService.shared().theme + }.assign(to: &$theme) } - @Published var themeId: ThemeIdentifier? + @Published var theme: Theme = ThemeService.shared().theme } diff --git a/Riot/Managers/Theme/ThemeIdentifier.swift b/Riot/Managers/Theme/ThemeIdentifier.swift index dbae22b17..5411a278d 100644 --- a/Riot/Managers/Theme/ThemeIdentifier.swift +++ b/Riot/Managers/Theme/ThemeIdentifier.swift @@ -40,4 +40,3 @@ extension ThemeIdentifier { ThemeService.shared().theme(withThemeId: self.rawValue) } } - diff --git a/Riot/Managers/Theme/ThemeService.swift b/Riot/Managers/Theme/ThemeService.swift index dd7c3d198..209812111 100644 --- a/Riot/Managers/Theme/ThemeService.swift +++ b/Riot/Managers/Theme/ThemeService.swift @@ -25,23 +25,3 @@ extension ThemeService { return ThemeIdentifier(rawValue: themeId) } } - -import SwiftUI -private struct ThemeKey: EnvironmentKey { - static let defaultValue = ThemeService.shared().theme -} - -@available(iOS 13.0, *) -extension EnvironmentValues { - var theme: Theme { - get { self[ThemeKey.self] } - set { self[ThemeKey.self] = newValue } - } -} - -@available(iOS 13.0, *) -extension View { - func theme(_ theme: Theme) -> some View { - environment(\.theme, theme) - } -} diff --git a/Riot/Modules/Common/Avatar/AvatarInputType.swift b/Riot/Modules/Common/Avatar/AvatarInputType.swift new file mode 100644 index 000000000..f0eff17fb --- /dev/null +++ b/Riot/Modules/Common/Avatar/AvatarInputType.swift @@ -0,0 +1,34 @@ +// +// 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 AvatarInputType { + var mxContentUri: String? { get } + var itemId: String { get } + var displayName: String? { get } +} + +struct AvatarInput: AvatarInputType { + let mxContentUri: String? + let itemId: String + let displayName: String? +} + +enum AvatarInputOption { + case swiftUI(AvatarInputType) + case uiKit(AvatarViewDataProtocol) +} diff --git a/Riot/Modules/Common/Avatar/AvatarService.swift b/Riot/Modules/Common/Avatar/AvatarService.swift new file mode 100644 index 000000000..e31aa06b5 --- /dev/null +++ b/Riot/Modules/Common/Avatar/AvatarService.swift @@ -0,0 +1,108 @@ +// +// 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 MatrixSDK +import Combine +import DesignKit + +/** + Provides a simple api to retrieve and cache avatar images + */ +protocol AvatarServiceType { + @available(iOS 14.0, *) + func avatarImage(inputData: AvatarInputType) -> AnyPublisher +} + +enum AvatarServiceError: Error { + case pathNotfound + case loadingImageFailed(Error?) +} + +class AvatarService: AvatarServiceType { + + private enum Constants { + static let mimeType = "image/jpeg" + static let thumbnailMethod = MXThumbnailingMethodCrop + static let avatarDownloadSize = AvatarSize.xxLarge.size + } + + let avatarGenerator: AvatarGenerator + let mediaManager: MXMediaManager + + init(avatarGenerator: AvatarGenerator, mediaManager: MXMediaManager) { + self.avatarGenerator = avatarGenerator + self.mediaManager = mediaManager + } + + @available(iOS 14.0, *) + func avatarImage(inputData: AvatarInputType) -> AnyPublisher { + + let generatedAvatar = AvatarGenerator.generateAvatar(forMatrixItem: inputData.itemId, withDisplayName: inputData.displayName) + guard let mxContentUri = inputData.mxContentUri else { + // No content URI just complete with the generated avatar + return Just(generatedAvatar) + .eraseToAnyPublisher() + } + + let cachePath = MXMediaManager.thumbnailCachePath( + forMatrixContentURI: mxContentUri, + andType: Constants.mimeType, + inFolder: nil, + toFitViewSize: Constants.avatarDownloadSize, + with: Constants.thumbnailMethod) + + if let image = MXMediaManager.loadThroughCache(withFilePath: cachePath) { + // Already cached, complete with the avatar + return Just(Self.orientImageUp(image: image)) + .eraseToAnyPublisher() + } + + let future = Future { promise in + self.mediaManager.downloadThumbnail( + fromMatrixContentURI: mxContentUri, + withType: Constants.mimeType, + inFolder: nil, + toFitViewSize: Constants.avatarDownloadSize, + with: Constants.thumbnailMethod) { path in + guard let path = path else { + promise(.failure(AvatarServiceError.pathNotfound)) + return + } + + let image = MXMediaManager.loadThroughCache(withFilePath: path) + promise(.success(Self.orientImageUp(image: image))) + } failure: { error in + promise(.failure(AvatarServiceError.loadingImageFailed(error))) + } + } + // First publish the generated avatar and then complete with the retrieved one + // In the case of an error retreiving the avatar also return generated one. + return future + .prepend(generatedAvatar) + .catch { _ -> Just in + MXLog.error("[AvatarService] Failed to retrieve avatar.") + // TODO: Report non-fatal error when we have Sentry or similar. + return Just(generatedAvatar) + } + .eraseToAnyPublisher() + } + + private static func orientImageUp(image: UIImage?) -> UIImage? { + guard let image = image?.cgImage else { return nil } + return UIImage(cgImage: image, scale: 1.0, orientation: .up) + } +} diff --git a/Riot/Modules/Common/Avatar/MockAvatarService.swift b/Riot/Modules/Common/Avatar/MockAvatarService.swift new file mode 100644 index 000000000..a29084a0a --- /dev/null +++ b/Riot/Modules/Common/Avatar/MockAvatarService.swift @@ -0,0 +1,30 @@ +// +// 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 Combine + +@available(iOS 14.0, *) +class MockAvatarService: AvatarServiceType { + static let example = MockAvatarService() + func avatarImage(inputData: AvatarInputType) -> AnyPublisher { + guard let image = AvatarGenerator.generateAvatar(forText: inputData.displayName ?? "") else { + fatalError() + } + return Just(image) + .eraseToAnyPublisher() + } +} diff --git a/Riot/Modules/Common/SwiftUI/VectorContentView.swift b/Riot/Modules/Common/SwiftUI/VectorContentView.swift new file mode 100644 index 000000000..c1502e19b --- /dev/null +++ b/Riot/Modules/Common/SwiftUI/VectorContentView.swift @@ -0,0 +1,39 @@ +// +// 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 + +/** + A Modifier to be called from the topmost SwiftUI view before being added to a HostViewController + Provides any app level configuration the SwiftUI hierarchy might need(E.g. to monitor theme changes). + */ +@available(iOS 14.0, *) +struct VectorContentModifier: ViewModifier { + + @StateObject var themeObservor = ThemeObserver.shared + + func body(content: Content) -> some View { + content + .theme(themeObservor.theme) + } +} + +@available(iOS 14.0, *) +extension View { + func vectorContent() -> some View { + self.modifier(VectorContentModifier()) + } +} diff --git a/Riot/Modules/Common/SwiftUI/VectorHostingViewController.swift b/Riot/Modules/Common/SwiftUI/VectorHostingViewController.swift new file mode 100644 index 000000000..d0f25d0f1 --- /dev/null +++ b/Riot/Modules/Common/SwiftUI/VectorHostingViewController.swift @@ -0,0 +1,64 @@ +// +// 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 + +/** + UIHostingController that applies some level-app specific configuration + (E.g. vectorContent modifier and themeing to the NavigationController container. + */ +@available(iOS 14.0, *) +class VectorHostingViewController: UIHostingController { + + init(rootView: Content) where Content: View { + self.theme = ThemeService.shared().theme + super.init(rootView: AnyView(rootView.vectorContent())) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("VectorHostingViewController does not currently support init from nibs") + } + + // MARK: Private + + private var theme: Theme + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func update(theme: Theme) { + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + } +} + diff --git a/Riot/Modules/Common/SwiftUI/VisibilityModifier.swift b/Riot/Modules/Common/SwiftUI/VisibilityModifier.swift new file mode 100644 index 000000000..be65c4b1a --- /dev/null +++ b/Riot/Modules/Common/SwiftUI/VisibilityModifier.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 Foundation +import SwiftUI + +/** + Used to modify the visibilty of a SwiftUI view. + `hidden` naming historically on iOS refers to a view that is not visible but is included in layout/constraints. i.e. takes up the space. + `gone` here refers to a view that is invisible and does not contribute to layout. Android uses the same naming as this. + */ +@available(iOS 14.0, *) +enum Visbility: Int { + case visible + case hidden + case gone +} + +@available(iOS 14.0, *) +struct VisbilityModifier: ViewModifier { + var visibilty: Visbility + func body(content: Content) -> some View { + if visibilty == .visible { + content + } else if visibilty == .hidden { + content.hidden() + } + } +} + +@available(iOS 14.0, *) +extension View { + func hidden(_ invisible: Bool) -> some View { + self.modifier(VisbilityModifier(visibilty: invisible ? .hidden : .visible)) + } + + func gone(_ hidden: Bool) -> some View { + self.modifier(VisbilityModifier(visibilty: hidden ? .gone : .visible)) + } + + func visbility(_ visibility: Visbility) -> some View { + self.modifier(VisbilityModifier(visibilty: visibility)) + } +} diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsCoordinator.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsCoordinator.swift index 49d4abdd8..0257a5993 100644 --- a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsCoordinator.swift +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsCoordinator.swift @@ -26,7 +26,6 @@ final class RoomNotificationSettingsCoordinator: RoomNotificationSettingsCoordin // MARK: Private private var roomNotificationSettingsViewModel: RoomNotificationSettingsViewModelType -// private let roomNotificationSettingsViewController: RoomNotificationSettingsViewController private let roomNotificationSettingsViewController: UIViewController // MARK: Public @@ -38,23 +37,37 @@ final class RoomNotificationSettingsCoordinator: RoomNotificationSettingsCoordin // MARK: - Setup - init(room: MXRoom, showAvatar: Bool = true) { - let repository = RoomNotificationSettingsService(room: room) + init(room: MXRoom, presentedModally: Bool = true) { + let roomNotificationService = RoomNotificationSettingsService(room: room) + let avatarService = AvatarService(avatarGenerator: AvatarGenerator(), mediaManager: room.mxSession.mediaManager) - let avatarData = showAvatar ? RoomAvatarViewData( - roomId: room.roomId, - displayName: room.summary.displayname, - avatarUrl: room.summary.avatar, - mediaManager: room.mxSession.mediaManager - ) : nil + let avatarData: AvatarInputOption? + let showAvatar = presentedModally + if #available(iOS 14.0.0, *) { + avatarData = showAvatar ? .swiftUI(AvatarInput(mxContentUri: room.summary.avatar, + itemId: room.roomId, + displayName: room.summary.displayname + )) : nil + } else { + avatarData = showAvatar ? .uiKit(RoomAvatarViewData( + roomId: room.roomId, + displayName: room.summary.displayname, + avatarUrl: room.summary.avatar, + mediaManager: room.mxSession.mediaManager + )) : nil + } - let roomNotificationSettingsViewModel = RoomNotificationSettingsViewModel(roomNotificationService: repository, roomEncrypted: room.summary.isEncrypted, avatarViewData: avatarData) + let roomNotificationSettingsViewModel = RoomNotificationSettingsViewModel( + roomNotificationService: roomNotificationService, + avatarService: avatarService, + avatarData: avatarData, + roomEncrypted: room.summary.isEncrypted) let viewController: UIViewController - if #available(iOS 13.0.0, *) { -// let sampleViewState = RoomNotificationSettingsViewState(roomEncrypted: true, saving: false, notificationState: .mute, avatarData: nil) - let view = RoomNotificationSettingsView(viewModel: roomNotificationSettingsViewModel, presentedModally: true) - viewController = UIHostingController(rootView: view) + if #available(iOS 14.0.0, *) { + let view = RoomNotificationSettingsView(viewModel: roomNotificationSettingsViewModel, presentedModally: presentedModally) + .vectorContent() + viewController = VectorHostingViewController(rootView: view) } else { viewController = RoomNotificationSettingsViewController.instantiate(with: roomNotificationSettingsViewModel) } diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewModel.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewModel.swift index 4c022e9fe..368aee78b 100644 --- a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewModel.swift +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewModel.swift @@ -18,6 +18,7 @@ import Foundation import SwiftUI +import Combine final class RoomNotificationSettingsViewModel: RoomNotificationSettingsViewModelType { @@ -26,39 +27,59 @@ final class RoomNotificationSettingsViewModel: RoomNotificationSettingsViewModel // MARK: Private private let roomNotificationService: RoomNotificationSettingsServiceType + private let avatarService: AvatarServiceType private var state: RoomNotificationSettingsViewState { willSet { update(viewState: newValue) } } - @available(iOS 13.0.0, *) + @available(iOS 14.0, *) @Published var viewState: RoomNotificationSettingsViewState! + @available(iOS 14.0, *) + lazy var bag = Set() // MARK: Public - + weak var viewDelegate: RoomNotificationSettingsViewModelViewDelegate? weak var coordinatorDelegate: RoomNotificationSettingsViewModelCoordinatorDelegate? // MARK: - Setup - init(roomNotificationService: RoomNotificationSettingsServiceType, roomEncrypted: Bool, avatarViewData: AvatarViewDataProtocol?) { + init( + roomNotificationService: RoomNotificationSettingsServiceType, + avatarService: AvatarServiceType, + avatarData: AvatarInputOption?, + roomEncrypted: Bool + ) { self.roomNotificationService = roomNotificationService + self.avatarService = avatarService let notificationState = Self.mapNotificationStateOnRead(encrypted: roomEncrypted, state: roomNotificationService.notificationState) - let initialState = RoomNotificationSettingsViewState(roomEncrypted: roomEncrypted, saving: false, notificationState: notificationState, avatarData: avatarViewData) + let initialState = RoomNotificationSettingsViewState(roomEncrypted: roomEncrypted, saving: false, notificationState: notificationState, avatar: nil) self.state = initialState - if #available(iOS 13.0.0, *) { + if #available(iOS 14.0, *) { self.viewState = initialState } self.roomNotificationService.observeNotificationState { [weak self] state in guard let self = self else { return } - self.state.notificationState = Self.mapNotificationStateOnRead(encrypted: roomEncrypted, state: state) } - + + if #available(iOS 14.0, *), + let avatarData = avatarData, + case let AvatarInputOption.swiftUI(data) = avatarData { + avatarService.avatarImage(inputData: data) + .sink{ image in + var newState = self.state + newState.avatar = image + newState.displayName = data.displayName + self.state = newState + } + .store(in: &bag) + } } // MARK: - Public @@ -95,11 +116,11 @@ final class RoomNotificationSettingsViewModel: RoomNotificationSettingsViewModel private func update(viewState: RoomNotificationSettingsViewState) { self.viewDelegate?.roomNotificationSettingsViewModel(self, didUpdateViewState: viewState) - if #available(iOS 13.0.0, *) { + if #available(iOS 14.0, *) { self.viewState = viewState } } } -@available(iOS 13.0, *) +@available(iOS 14.0, *) extension RoomNotificationSettingsViewModel: ObservableObject {} diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewStateType.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewStateType.swift index 56de53b13..20c244dfd 100644 --- a/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewStateType.swift +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationSettingsViewStateType.swift @@ -23,6 +23,12 @@ struct RoomNotificationSettingsViewState: RoomNotificationSettingsViewStateType let roomEncrypted: Bool var saving: Bool var notificationState: RoomNotificationState + var avatar: UIImage? + var displayName: String? + var legacyAvatarData: AvatarViewDataProtocol? +} + +extension RoomNotificationSettingsViewState { var notificationOptions: [RoomNotificationState] { if roomEncrypted { return [.all, .mute] @@ -30,7 +36,6 @@ struct RoomNotificationSettingsViewState: RoomNotificationSettingsViewStateType return RoomNotificationState.allCases } } - let avatarData: AvatarViewDataProtocol? } protocol RoomNotificationSettingsViewStateType { @@ -38,5 +43,6 @@ protocol RoomNotificationSettingsViewStateType { var roomEncrypted: Bool { get } var notificationOptions: [RoomNotificationState] { get } var notificationState: RoomNotificationState { get } - var avatarData: AvatarViewDataProtocol? { get } + var avatar: UIImage? { get } + var legacyAvatarData: AvatarViewDataProtocol? { get } } diff --git a/Riot/Modules/Room/NotificationSettings/RoomNotificationState.swift b/Riot/Modules/Room/NotificationSettings/RoomNotificationState.swift index 054993117..5937eece7 100644 --- a/Riot/Modules/Room/NotificationSettings/RoomNotificationState.swift +++ b/Riot/Modules/Room/NotificationSettings/RoomNotificationState.swift @@ -16,8 +16,14 @@ import Foundation -enum RoomNotificationState: CaseIterable { +enum RoomNotificationState: Int { case all case mentionsAndKeywordsOnly case mute } + +extension RoomNotificationState: CaseIterable { } + +extension RoomNotificationState: Identifiable { + var id: Int { self.rawValue } +} diff --git a/Riot/Modules/Room/NotificationSettings/SwiftUI/FormPickerItem.swift b/Riot/Modules/Room/NotificationSettings/SwiftUI/FormPickerItem.swift deleted file mode 100644 index 3ed136271..000000000 --- a/Riot/Modules/Room/NotificationSettings/SwiftUI/FormPickerItem.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// 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 - -@available(iOS 13.0, *) -struct FormPickerItem: View { - - typealias ClickCallback = () -> Void - - @Environment(\.theme) var theme: Theme - - var title: String - var selected: Bool - var onClick: ClickCallback? - - var body: some View { - HStack { - Text(title) - .font(Font(theme.fonts.body)) - .foregroundColor(Color(theme.textPrimaryColor)) - Spacer() - if selected { - Image("checkmark") - .foregroundColor(Color(theme.tintColor)) - } - } - .listRowBackground(Color(theme.backgroundColor)) - .contentShape(Rectangle()) - .onTapGesture { - onClick?() - } - } -} - -@available(iOS 13.0, *) -struct FormPickerCell_Previews: PreviewProvider { - static var previews: some View { - List { - FormPickerItem(title: "Item 1", selected: true, onClick: nil) - FormPickerItem(title: "Item 2", selected: false, onClick: nil) - FormPickerItem(title: "Item 3", selected: false, onClick: nil) - }.listStyle(GroupedListStyle()) - - } -} diff --git a/Riot/Modules/Room/NotificationSettings/SwiftUI/FormPickerItemView.swift b/Riot/Modules/Room/NotificationSettings/SwiftUI/FormPickerItemView.swift new file mode 100644 index 000000000..b3ae23500 --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/SwiftUI/FormPickerItemView.swift @@ -0,0 +1,66 @@ +// +// 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 + +@available(iOS 14.0, *) +struct FormPickerItemView: View { + + typealias ClickCallback = () -> Void + + @Environment(\.theme) var theme: Theme + + var title: String + var selected: Bool + var onClick: ClickCallback? + + var body: some View { + Button { + onClick?() + } label: { + VStack { + Spacer() + HStack { + Text(title) + Spacer() + Image("checkmark") + .foregroundColor(Color(theme.tintColor)) + .gone(!selected) + } + .padding(.trailing) + Spacer() + Divider() + } + .padding(.leading) + } + .buttonStyle(VectorFormItemButtonStyle()) + .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, idealHeight: 44, alignment: .leading) + .fixedSize(horizontal: false, vertical: true) + } +} + +@available(iOS 14.0, *) +struct FormPickerCell_Previews: PreviewProvider { + static let items = ["Item 1", "Item 2", "Item 3"] + static var selected: String = items[0] + static var previews: some View { + VectorFormView { + ForEach(items, id: \.self) { item in + FormPickerItemView(title: item, selected: selected == item) + } + } + } +} diff --git a/Riot/Modules/Room/NotificationSettings/SwiftUI/FormSectionFooter.swift b/Riot/Modules/Room/NotificationSettings/SwiftUI/FormSectionFooterView.swift similarity index 63% rename from Riot/Modules/Room/NotificationSettings/SwiftUI/FormSectionFooter.swift rename to Riot/Modules/Room/NotificationSettings/SwiftUI/FormSectionFooterView.swift index 83d821f61..2ba397034 100644 --- a/Riot/Modules/Room/NotificationSettings/SwiftUI/FormSectionFooter.swift +++ b/Riot/Modules/Room/NotificationSettings/SwiftUI/FormSectionFooterView.swift @@ -16,8 +16,8 @@ import SwiftUI -@available(iOS 13.0, *) -struct FormSectionFooter: View { +@available(iOS 14.0, *) +struct FormSectionFooterView: View { @Environment(\.theme) var theme: Theme var text: String @@ -26,19 +26,21 @@ struct FormSectionFooter: View { Text(text) .foregroundColor(Color(theme.textSecondaryColor)) .padding(.top) + .padding(.leading) + .padding(.trailing) .font(Font(theme.fonts.callout)) } } -@available(iOS 13.0, *) +@available(iOS 14.0, *) struct FormSectionFooter_Previews: PreviewProvider { static var previews: some View { - List { - SwiftUI.Section(footer: FormSectionFooter(text: "Footer Text")) { - Text("Item 1") - Text("Item 2") - Text("Item 3") + VectorFormView { + SwiftUI.Section(footer: FormSectionFooterView(text: "Please note that mentions & keyword notifications are not available in encrypted rooms on mobile.")) { + FormPickerItemView(title: "Item 1", selected: false) + FormPickerItemView(title: "Item 2", selected: false) + FormPickerItemView(title: "Item 3", selected: false) } - }.listStyle(GroupedListStyle()) + } } } diff --git a/Riot/Modules/Room/NotificationSettings/SwiftUI/FormSectionHeader.swift b/Riot/Modules/Room/NotificationSettings/SwiftUI/FormSectionHeaderView.swift similarity index 63% rename from Riot/Modules/Room/NotificationSettings/SwiftUI/FormSectionHeader.swift rename to Riot/Modules/Room/NotificationSettings/SwiftUI/FormSectionHeaderView.swift index ea3ffadb8..8c6a9af80 100644 --- a/Riot/Modules/Room/NotificationSettings/SwiftUI/FormSectionHeader.swift +++ b/Riot/Modules/Room/NotificationSettings/SwiftUI/FormSectionHeaderView.swift @@ -16,8 +16,8 @@ import SwiftUI -@available(iOS 13.0, *) -struct FormSectionHeader: View { +@available(iOS 14.0, *) +struct FormSectionHeaderView: View { @Environment(\.theme) var theme: Theme var text: String @@ -26,19 +26,23 @@ struct FormSectionHeader: View { Text(text) .foregroundColor(Color(theme.textSecondaryColor)) .padding(.top) + .padding(.leading) + .padding(.bottom, 8) .font(Font(theme.fonts.footnote)) + .autocapitalization(.allCharacters) + .textCase(.uppercase) } } -@available(iOS 13.0, *) +@available(iOS 14.0, *) struct FormSectionHeader_Previews: PreviewProvider { static var previews: some View { - List { - SwiftUI.Section(header: FormSectionHeader(text: "Section Header")) { - Text("Item 1") - Text("Item 2") - Text("Item 3") + VectorFormView { + SwiftUI.Section(header: FormSectionHeaderView(text: "Section Header")) { + FormPickerItemView(title: "Item 1", selected: false) + FormPickerItemView(title: "Item 2", selected: false) + FormPickerItemView(title: "Item 3", selected: false) } - }.listStyle(GroupedListStyle()) + } } } diff --git a/Riot/Modules/Room/NotificationSettings/SwiftUI/RoomNotificationSettingsHeaderView.swift b/Riot/Modules/Room/NotificationSettings/SwiftUI/RoomNotificationSettingsHeaderView.swift new file mode 100644 index 000000000..bd0b3f856 --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/SwiftUI/RoomNotificationSettingsHeaderView.swift @@ -0,0 +1,50 @@ +// +// 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 + +@available(iOS 14.0, *) +struct RoomNotificationSettingsHeaderView: View { + + @Environment(\.theme) var theme: Theme + var image: UIImage + var displayName: String? + + var body: some View { + HStack { + Spacer() + VStack(alignment: .center) { + VectorAvatarView(image: image, size: .xxLarge) + if let displayName = displayName { + Text(displayName) + .font(Font(theme.fonts.title3SB)) + .foregroundColor(Color(theme.textPrimaryColor)) + .textCase(nil) + } + } + Spacer() + }.padding(.top, 36) + } +} + +@available(iOS 14.0, *) +struct RoomNotificationSettingsHeaderView_Previews: PreviewProvider { + static let image = UIImage(imageLiteralResourceName: "app_symbol") + static let name = "Element" + static var previews: some View { + RoomNotificationSettingsHeaderView(image: image, displayName: name) + } +} diff --git a/Riot/Modules/Room/NotificationSettings/SwiftUI/RoomNotificationSettingsView.swift b/Riot/Modules/Room/NotificationSettings/SwiftUI/RoomNotificationSettingsView.swift index 358c0d317..9c8c2cdff 100644 --- a/Riot/Modules/Room/NotificationSettings/SwiftUI/RoomNotificationSettingsView.swift +++ b/Riot/Modules/Room/NotificationSettings/SwiftUI/RoomNotificationSettingsView.swift @@ -16,32 +16,40 @@ import SwiftUI -@available(iOS 13.0.0, *) +@available(iOS 14.0.0, *) struct RoomNotificationSettingsView: View { @Environment(\.theme) var theme: Theme @ObservedObject var viewModel: RoomNotificationSettingsViewModel + let presentedModally: Bool - @State var notificationState: RoomNotificationState = RoomNotificationState.all - - - var body: some View { - let leftButton = presentedModally ? + @ViewBuilder + var leftButton: some View { + if presentedModally { SwiftUI.Button(VectorL10n.cancel) { viewModel.process(viewAction: .cancel) } - : nil - let rightButton = SwiftUI.Button(VectorL10n.save) { + } + } + + var rightButton: some View { + Button(VectorL10n.save) { viewModel.process(viewAction: .save) } - VectorForm { + } + + var body: some View { + VectorFormView { + if let image = viewModel.viewState.avatar { + RoomNotificationSettingsHeaderView(image: image, displayName: viewModel.viewState.displayName) + } SwiftUI.Section( - header: FormSectionHeader(text: VectorL10n.roomNotifsSettingsNotifyMeFor), - footer: FormSectionFooter(text: viewModel.viewState.roomEncryptedString) + header: FormSectionHeaderView(text: VectorL10n.roomNotifsSettingsNotifyMeFor), + footer: FormSectionFooterView(text: viewModel.viewState.roomEncryptedString) ) { ForEach(viewModel.viewState.notificationOptions) { option in - FormPickerItem(title: option.title, selected: viewModel.viewState.notificationState == option) { + FormPickerItemView(title: option.title, selected: viewModel.viewState.notificationState == option) { viewModel.process(viewAction: .selectNotificationState(option)) } } @@ -56,8 +64,6 @@ struct RoomNotificationSettingsView: View { } } } - - fileprivate extension RoomNotificationState { var title: String { @@ -78,26 +84,27 @@ fileprivate extension RoomNotificationSettingsViewState { } } -extension RoomNotificationState: Identifiable { - var id: String { UUID().uuidString } -} - - @available(iOS 14.0, *) struct RoomNotificationSettingsView_Previews: PreviewProvider { - + static let mockViewModel = RoomNotificationSettingsViewModel( roomNotificationService: MockRoomNotificationSettingsService.example, - roomEncrypted: true, - avatarViewData: nil + avatarService: MockAvatarService.example, + avatarData: .swiftUI(AvatarInput(mxContentUri: nil, itemId: "", displayName: "Alice")), + roomEncrypted: true ) - + static var previews: some View { Group { NavigationView { RoomNotificationSettingsView(viewModel: mockViewModel, presentedModally: true) .navigationBarTitleDisplayMode(.inline) } + NavigationView { + RoomNotificationSettingsView(viewModel: mockViewModel, presentedModally: true) + .navigationBarTitleDisplayMode(.inline) + .theme(ThemeIdentifier.dark.theme) + } } } } diff --git a/Riot/Modules/Room/NotificationSettings/SwiftUI/VectorAvatarView.swift b/Riot/Modules/Room/NotificationSettings/SwiftUI/VectorAvatarView.swift new file mode 100644 index 000000000..14d2f077b --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/SwiftUI/VectorAvatarView.swift @@ -0,0 +1,44 @@ +// +// 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 DesignKit + +@available(iOS 14.0, *) +struct VectorAvatarView: View { + + var image: UIImage + var size: AvatarSize + + var body: some View { + Image(uiImage: image) + .resizable() + .frame(width: CGFloat(size.rawValue), height: CGFloat(size.rawValue), alignment: .center) + .clipShape(Circle()) + } +} + +@available(iOS 14.0, *) +struct AvatarView_Previews: PreviewProvider { + static let image = UIImage(imageLiteralResourceName: "app_symbol") + static var previews: some View { + VStack { + VectorAvatarView(image: image, size: .xSmall) + VectorAvatarView(image: image, size: .medium) + VectorAvatarView(image: image, size: .xLarge) + } + } +} diff --git a/Riot/Modules/Room/NotificationSettings/SwiftUI/VectorForm.swift b/Riot/Modules/Room/NotificationSettings/SwiftUI/VectorForm.swift deleted file mode 100644 index ce5885238..000000000 --- a/Riot/Modules/Room/NotificationSettings/SwiftUI/VectorForm.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// 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 - -@available(iOS 13.0, *) -struct VectorForm: View { - - @Environment(\.theme) var theme: Theme - var content: () -> Content - - var body: some View { - List(content: content) - .listRowBackground(Color(theme.backgroundColor)) - .listStyle(GroupedListStyle()) - .onAppear { - UITableView.appearance().backgroundColor = theme.baseColor - UINavigationBar.appearance().barTintColor = theme.baseColor - UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: theme.textPrimaryColor] - UINavigationBar.appearance().isTranslucent = false - UINavigationBar.appearance().setBackgroundImage(UIImage(), for: UIBarMetrics.default) - UINavigationBar.appearance().shadowImage = UIImage() - } - } -} - -@available(iOS 13.0, *) -struct VectorForm_Previews: PreviewProvider { - static var previews: some View { - VectorForm { - Text("Item 1") - } - } -} diff --git a/Riot/Modules/Room/NotificationSettings/SwiftUI/VectorFormItemButtonStyle.swift b/Riot/Modules/Room/NotificationSettings/SwiftUI/VectorFormItemButtonStyle.swift new file mode 100644 index 000000000..19ff7b9db --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/SwiftUI/VectorFormItemButtonStyle.swift @@ -0,0 +1,29 @@ +// +// 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 + +@available(iOS 14.0, *) +struct VectorFormItemButtonStyle: ButtonStyle { + @Environment(\.theme) var theme: Theme + func makeBody(configuration: Self.Configuration) -> some View { + configuration.label + .background(configuration.isPressed ? Color(theme.selectedBackgroundColor) : Color(theme.backgroundColor)) + .foregroundColor(Color(theme.textPrimaryColor)) + .font(Font(theme.fonts.body)) + } +} diff --git a/Riot/Modules/Room/NotificationSettings/SwiftUI/VectorFormView.swift b/Riot/Modules/Room/NotificationSettings/SwiftUI/VectorFormView.swift new file mode 100644 index 000000000..170055c3e --- /dev/null +++ b/Riot/Modules/Room/NotificationSettings/SwiftUI/VectorFormView.swift @@ -0,0 +1,63 @@ +// +// 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 + +@available(iOS 14.0, *) +struct VectorFormView: View { + + @Environment(\.theme) var theme: Theme + var content: () -> Content + + init(@ViewBuilder content: @escaping () -> Content) { + self.content = content + } + + var body: some View { + ScrollView { + VStack(alignment: .leading, spacing: 0, content: content) + } + .frame( + minWidth: 0, + maxWidth: .infinity, + minHeight: 0, + maxHeight: .infinity, + alignment: .top + ) + .background(Color(theme.baseColor)) + .edgesIgnoringSafeArea(.bottom) + + } +} + +@available(iOS 14.0, *) +struct VectorForm_Previews: PreviewProvider { + + static var previews: some View { + Group { + VectorFormView { + SwiftUI.Section(header: FormSectionHeaderView(text: "Section Header")) { + FormPickerItemView(title: "Item 1", selected: true) + FormPickerItemView(title: "Item 2", selected: false) + FormPickerItemView(title: "Item 3", selected: false) + } + } + VectorFormView { + FormPickerItemView(title: "Item 1", selected: true) + }.theme(ThemeIdentifier.dark.theme) + } + } +} diff --git a/Riot/Modules/Room/NotificationSettings/UIKit/RoomNotificationSettingsViewController.swift b/Riot/Modules/Room/NotificationSettings/UIKit/RoomNotificationSettingsViewController.swift index 3e2adf89e..06171bc49 100644 --- a/Riot/Modules/Room/NotificationSettings/UIKit/RoomNotificationSettingsViewController.swift +++ b/Riot/Modules/Room/NotificationSettings/UIKit/RoomNotificationSettingsViewController.swift @@ -144,7 +144,7 @@ final class RoomNotificationSettingsViewController: UIViewController { activityPresenter.removeCurrentActivityIndicator(animated: true) } self.viewState = viewState - if let avatarData = viewState.avatarData { + if let avatarData = viewState.legacyAvatarData { mainTableView.tableHeaderView = avatarView avatarView.configure(viewData: avatarData) avatarView.update(theme: theme) diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift index 569803fbf..e542f5521 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift @@ -138,7 +138,7 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType { } private func createRoomNotificationSettingsCoordinator() -> RoomNotificationSettingsCoordinator { - let coordinator = RoomNotificationSettingsCoordinator(room: room, showAvatar: false) + let coordinator = RoomNotificationSettingsCoordinator(room: room, presentedModally: false) coordinator.delegate = self return coordinator } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/Views/RoomInfoBasicView.xib b/Riot/Modules/Room/RoomInfo/RoomInfoList/Views/RoomInfoBasicView.xib index ee4411e67..6713db5b6 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/Views/RoomInfoBasicView.xib +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/Views/RoomInfoBasicView.xib @@ -1,9 +1,10 @@ - + - + + @@ -64,7 +65,7 @@ + + + + + + + + + +