merge, finish swiftui and add theming

This commit is contained in:
David Langley
2021-08-06 00:27:13 +01:00
175 changed files with 4808 additions and 1281 deletions
@@ -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 Foundation
class MockRoomNotificationSettingsService: RoomNotificationSettingsServiceType {
static let example = MockRoomNotificationSettingsService(initialState: .all)
var listener: RoomNotificationStateCallback?
var notificationState: RoomNotificationState
init(initialState: RoomNotificationState) {
notificationState = initialState
}
func observeNotificationState(listener: @escaping RoomNotificationStateCallback) {
self.listener = listener
}
func update(state: RoomNotificationState, completion: @escaping UpdateRoomNotificationStateCompletion) {
self.notificationState = state
completion()
listener?(state)
}
}
@@ -18,6 +18,7 @@
import Foundation
import UIKit
import SwiftUI
final class RoomNotificationSettingsCoordinator: RoomNotificationSettingsCoordinatorType {
@@ -25,7 +26,8 @@ final class RoomNotificationSettingsCoordinator: RoomNotificationSettingsCoordin
// MARK: Private
private var roomNotificationSettingsViewModel: RoomNotificationSettingsViewModelType
private let roomNotificationSettingsViewController: RoomNotificationSettingsViewController
// private let roomNotificationSettingsViewController: RoomNotificationSettingsViewController
private let roomNotificationSettingsViewController: UIViewController
// MARK: Public
@@ -45,10 +47,19 @@ final class RoomNotificationSettingsCoordinator: RoomNotificationSettingsCoordin
avatarUrl: room.summary.avatar,
mediaManager: room.mxSession.mediaManager
) : nil
let roomNotificationSettingsViewModel = RoomNotificationSettingsViewModel(roomNotificationService: repository, roomEncrypted: room.summary.isEncrypted, avatarViewData: avatarData)
let roomNotificationSettingsViewController = RoomNotificationSettingsViewController.instantiate(with: roomNotificationSettingsViewModel)
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)
} else {
viewController = RoomNotificationSettingsViewController.instantiate(with: roomNotificationSettingsViewModel)
}
self.roomNotificationSettingsViewModel = roomNotificationSettingsViewModel
self.roomNotificationSettingsViewController = roomNotificationSettingsViewController
self.roomNotificationSettingsViewController = viewController
}
// MARK: - Public methods
@@ -1,79 +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
struct RoomNotificationSettingsView: View {
var viewState: RoomNotificationSettingsViewState
var didSelect: ((RoomNotificationState) -> Void)?
var body: some View {
List {
SwiftUI.Section(
header: Text(VectorL10n.roomNotifsSettingsNotifyMeFor),
footer: Text(viewState.roomEncryptedString)) {
ForEach(viewState.notificationOptions) { option in
HStack {
Text(option.title)
Spacer()
if viewState.notificationState == option {
Image("checkmark").foregroundColor(/*@START_MENU_TOKEN@*/.blue/*@END_MENU_TOKEN@*/)
}
}.onTapGesture {
didSelect?(option)
}
}
}
}.listStyle(GroupedListStyle())
}
}
struct RoomNotificationSettingsView_Previews: PreviewProvider {
static let viewState = RoomNotificationSettingsViewState(
roomEncrypted: true,
saving: true,
notificationState: .all,
avatarData: nil)
static var previews: some View {
RoomNotificationSettingsView(viewState: viewState)
}
}
fileprivate extension RoomNotificationState {
var title: String {
switch self {
case .all:
return VectorL10n.roomNotifsSettingsAllMessages
case .mentionsAndKeywordsOnly:
return VectorL10n.roomNotifsSettingsMentionsAndKeywords
case .mute:
return VectorL10n.roomNotifsSettingsNone
}
}
}
fileprivate extension RoomNotificationSettingsViewState {
var roomEncryptedString: String {
roomEncrypted ? VectorL10n.roomNotifsSettingsEncryptedRoomNotice : ""
}
}
extension RoomNotificationState: Identifiable {
var id: String { UUID().uuidString }
}
@@ -17,6 +17,7 @@
*/
import Foundation
import SwiftUI
final class RoomNotificationSettingsViewModel: RoomNotificationSettingsViewModelType {
@@ -30,6 +31,9 @@ final class RoomNotificationSettingsViewModel: RoomNotificationSettingsViewModel
update(viewState: newValue)
}
}
@available(iOS 13.0.0, *)
@Published var viewState: RoomNotificationSettingsViewState!
// MARK: Public
weak var viewDelegate: RoomNotificationSettingsViewModelViewDelegate?
@@ -42,12 +46,19 @@ final class RoomNotificationSettingsViewModel: RoomNotificationSettingsViewModel
self.roomNotificationService = roomNotificationService
let notificationState = Self.mapNotificationStateOnRead(encrypted: roomEncrypted, state: roomNotificationService.notificationState)
self.state = RoomNotificationSettingsViewState(roomEncrypted: roomEncrypted, saving: false, notificationState: notificationState, avatarData: avatarViewData)
let initialState = RoomNotificationSettingsViewState(roomEncrypted: roomEncrypted, saving: false, notificationState: notificationState, avatarData: avatarViewData)
self.state = initialState
if #available(iOS 13.0.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)
}
}
// MARK: - Public
@@ -81,9 +92,14 @@ final class RoomNotificationSettingsViewModel: RoomNotificationSettingsViewModel
}
}
private func update(viewState: RoomNotificationSettingsViewStateType) {
private func update(viewState: RoomNotificationSettingsViewState) {
self.viewDelegate?.roomNotificationSettingsViewModel(self, didUpdateViewState: viewState)
if #available(iOS 13.0.0, *) {
self.viewState = viewState
}
}
}
@available(iOS 13.0, *)
extension RoomNotificationSettingsViewModel: ObservableObject {}
@@ -17,6 +17,7 @@
*/
import Foundation
import SwiftUI
protocol RoomNotificationSettingsViewModelViewDelegate: AnyObject {
func roomNotificationSettingsViewModel(_ viewModel: RoomNotificationSettingsViewModelType, didUpdateViewState viewState: RoomNotificationSettingsViewStateType)
@@ -264,11 +264,17 @@ fileprivate extension MXRoom {
}
var overridePushRule: MXPushRule? {
getRoomRule(from: mxSession.notificationCenter.rules.global.override)
guard let overrideRules = mxSession.notificationCenter.rules.global.override else {
return nil
}
return getRoomRule(from: overrideRules)
}
var roomPushRule: MXPushRule? {
getRoomRule(from: mxSession.notificationCenter.rules.global.room)
guard let roomRules = mxSession.notificationCenter.rules.global.room else {
return nil
}
return getRoomRule(from: roomRules)
}
var notificationState: RoomNotificationState {
@@ -0,0 +1,59 @@
//
// 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,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
@available(iOS 13.0, *)
struct FormSectionFooter: View {
@Environment(\.theme) var theme: Theme
var text: String
var body: some View {
Text(text)
.foregroundColor(Color(theme.textSecondaryColor))
.padding(.top)
.font(Font(theme.fonts.callout))
}
}
@available(iOS 13.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")
}
}.listStyle(GroupedListStyle())
}
}
@@ -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
@available(iOS 13.0, *)
struct FormSectionHeader: View {
@Environment(\.theme) var theme: Theme
var text: String
var body: some View {
Text(text)
.foregroundColor(Color(theme.textSecondaryColor))
.padding(.top)
.font(Font(theme.fonts.footnote))
}
}
@available(iOS 13.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")
}
}.listStyle(GroupedListStyle())
}
}
@@ -0,0 +1,103 @@
//
// 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.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 ?
SwiftUI.Button(VectorL10n.cancel) {
viewModel.process(viewAction: .cancel)
}
: nil
let rightButton = SwiftUI.Button(VectorL10n.save) {
viewModel.process(viewAction: .save)
}
VectorForm {
SwiftUI.Section(
header: FormSectionHeader(text: VectorL10n.roomNotifsSettingsNotifyMeFor),
footer: FormSectionFooter(text: viewModel.viewState.roomEncryptedString)
) {
ForEach(viewModel.viewState.notificationOptions) { option in
FormPickerItem(title: option.title, selected: viewModel.viewState.notificationState == option) {
viewModel.process(viewAction: .selectNotificationState(option))
}
}
}
}
.navigationBarTitle(VectorL10n.roomDetailsNotifs)
.navigationBarItems(
leading: leftButton,
trailing: rightButton
).onAppear {
viewModel.process(viewAction: .load)
}
}
}
fileprivate extension RoomNotificationState {
var title: String {
switch self {
case .all:
return VectorL10n.roomNotifsSettingsAllMessages
case .mentionsAndKeywordsOnly:
return VectorL10n.roomNotifsSettingsMentionsAndKeywords
case .mute:
return VectorL10n.roomNotifsSettingsNone
}
}
}
fileprivate extension RoomNotificationSettingsViewState {
var roomEncryptedString: String {
roomEncrypted ? VectorL10n.roomNotifsSettingsEncryptedRoomNotice : ""
}
}
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
)
static var previews: some View {
Group {
NavigationView {
RoomNotificationSettingsView(viewModel: mockViewModel, presentedModally: true)
.navigationBarTitleDisplayMode(.inline)
}
}
}
}
@@ -0,0 +1,47 @@
//
// 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")
}
}
}