mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-18 07:28:28 +02:00
Add theming support, and support for other top level configuration to swiftui views with VectorHostingViewController, VectorContentModifier. Add VisibilityModifier. Move from List to VStack.
This commit is contained in:
35
DesignKit/Source/AvatarSize.swift
Normal file
35
DesignKit/Source/AvatarSize.swift
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
35
Riot/Categories/Combine/Publisher.swift
Normal file
35
Riot/Categories/Combine/Publisher.swift
Normal file
@@ -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<T: AnyObject>(
|
||||
to keyPath: ReferenceWritableKeyPath<T, Output>,
|
||||
on object: T
|
||||
) -> AnyCancellable {
|
||||
sink { [weak object] value in
|
||||
object?[keyPath: keyPath] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Riot/Managers/Theme/SwiftUI/ThemeKey.swift
Normal file
37
Riot/Managers/Theme/SwiftUI/ThemeKey.swift
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -40,4 +40,3 @@ extension ThemeIdentifier {
|
||||
ThemeService.shared().theme(withThemeId: self.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
34
Riot/Modules/Common/Avatar/AvatarInputType.swift
Normal file
34
Riot/Modules/Common/Avatar/AvatarInputType.swift
Normal file
@@ -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)
|
||||
}
|
||||
108
Riot/Modules/Common/Avatar/AvatarService.swift
Normal file
108
Riot/Modules/Common/Avatar/AvatarService.swift
Normal file
@@ -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<UIImage?, Never>
|
||||
}
|
||||
|
||||
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<UIImage?, Never> {
|
||||
|
||||
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<UIImage?, Error> { 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<UIImage?> 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)
|
||||
}
|
||||
}
|
||||
30
Riot/Modules/Common/Avatar/MockAvatarService.swift
Normal file
30
Riot/Modules/Common/Avatar/MockAvatarService.swift
Normal file
@@ -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<UIImage?, Never> {
|
||||
guard let image = AvatarGenerator.generateAvatar(forText: inputData.displayName ?? "") else {
|
||||
fatalError()
|
||||
}
|
||||
return Just(image)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
39
Riot/Modules/Common/SwiftUI/VectorContentView.swift
Normal file
39
Riot/Modules/Common/SwiftUI/VectorContentView.swift
Normal file
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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<AnyView> {
|
||||
|
||||
init<Content>(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
57
Riot/Modules/Common/SwiftUI/VisibilityModifier.swift
Normal file
57
Riot/Modules/Common/SwiftUI/VisibilityModifier.swift
Normal file
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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<AnyCancellable>()
|
||||
|
||||
// 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 {}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Content: View>: 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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<Content: View>: 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -64,7 +65,7 @@
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" text="Label" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ekk-PI-fnh">
|
||||
<rect key="frame" x="156" y="97" width="50.5" height="24"/>
|
||||
<rect key="frame" x="156" y="97" width="50" height="24"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="20"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -87,7 +88,7 @@
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" verticalHuggingPriority="252" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" editable="NO" text="Lorem ipsum" translatesAutoresizingMaskIntoConstraints="NO" id="DCx-MH-TSC" customClass="ReadMoreTextView" customModule="ReadMoreTextView">
|
||||
<rect key="frame" x="0.0" y="36" width="362" height="34"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" systemColor="labelColor"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
<dataDetectorType key="dataDetectorTypes" phoneNumber="YES" link="YES" address="YES" calendarEvent="YES" shipmentTrackingNumber="YES" flightNumber="YES"/>
|
||||
@@ -170,4 +171,14 @@
|
||||
<point key="canvasLocation" x="155.07246376811597" y="-254.12946428571428"/>
|
||||
</view>
|
||||
</objects>
|
||||
<designables>
|
||||
<designable name="DCx-MH-TSC">
|
||||
<size key="intrinsicContentSize" width="-1" height="34"/>
|
||||
</designable>
|
||||
</designables>
|
||||
<resources>
|
||||
<systemColor name="labelColor">
|
||||
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
Reference in New Issue
Block a user