Improve folder name

This commit is contained in:
David Langley
2021-09-07 23:04:52 +01:00
parent 24b8e69363
commit e6d5ab117d
14 changed files with 0 additions and 0 deletions
@@ -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 UIKit
import SwiftUI
final class TemplateUserProfileCoordinator: Coordinator {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let templateUserProfileViewController: UIViewController
private var templateUserProfileViewModel: TemplateUserProfileViewModelProtocol
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
var completion: (() -> Void)?
// MARK: - Setup
@available(iOS 14.0, *)
init(session: MXSession) {
self.session = session
let viewModel = TemplateUserProfileViewModel(userService: MXTemplateUserProfileService(session: session))
let view = TemplateUserProfile(viewModel: viewModel)
.addDependency(MXAvatarService.instantiate(mediaManager: session.mediaManager))
templateUserProfileViewModel = viewModel
templateUserProfileViewController = VectorHostingController(rootView: view)
}
// MARK: - Public methods
func start() {
templateUserProfileViewModel.completion = { [weak self] result in
guard let self = self else { return }
switch result {
case .cancel, .done:
self.completion?()
break
}
}
}
func toPresentable() -> UIViewController {
return self.templateUserProfileViewController
}
}
@@ -0,0 +1,42 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
enum TemplateUserProfilePresence {
case online
case idle
case offline
}
extension TemplateUserProfilePresence {
var title: String {
switch self {
case .online:
return VectorL10n.roomParticipantsOnline
case .idle:
return VectorL10n.roomParticipantsIdle
case .offline:
return VectorL10n.roomParticipantsOffline
}
}
}
extension TemplateUserProfilePresence: CaseIterable { }
extension TemplateUserProfilePresence: Identifiable {
var id: Self { self }
}
@@ -0,0 +1,21 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
enum TemplateUserProfileStateAction {
case updatePresence(TemplateUserProfilePresence)
}
@@ -0,0 +1,22 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
enum TemplateUserProfileViewAction {
case cancel
case done
}
@@ -0,0 +1,22 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
enum TemplateUserProfileViewModelResult {
case cancel
case done
}
@@ -0,0 +1,23 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
struct TemplateUserProfileViewState {
let avatar: AvatarInputType?
let displayName: String?
var presence: TemplateUserProfilePresence = .offline
}
@@ -0,0 +1,83 @@
//
// 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 MXTemplateUserProfileService: TemplateUserProfileServiceProtocol {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var listenerReference: Any!
@Published private var presence: TemplateUserProfilePresence = .offline
// MARK: Public
var userId: String {
return session.myUser.userId
}
var displayName: String? {
session.myUser.displayname
}
var avatarUrl: String? {
session.myUser.avatarUrl
}
var presencePublisher: AnyPublisher<TemplateUserProfilePresence, Never> {
$presence.eraseToAnyPublisher()
}
// MARK: - Setup
init(session: MXSession) {
self.session = session
let listenerReference = session.myUser.listen { [weak self] event in
guard let self = self,
let event = event,
case .presence = MXEventType(identifier: event.eventId)
else { return }
self.presence = TemplateUserProfilePresence(mxPresence: self.session.myUser.presence)
}
self.listenerReference = listenerReference
}
deinit {
session.myUser.removeListener(listenerReference)
}
}
fileprivate extension TemplateUserProfilePresence {
init(mxPresence: MXPresence) {
switch mxPresence {
case MXPresenceOnline:
self = .online
case MXPresenceUnavailable:
self = .idle
case MXPresenceOffline, MXPresenceUnknown:
self = .offline
default:
self = .offline
}
}
}
@@ -0,0 +1,33 @@
//
// 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 MockTemplateUserProfileService: TemplateUserProfileServiceProtocol {
static let example = MockTemplateUserProfileService()
@Published var presence: TemplateUserProfilePresence = .online
var presencePublisher: AnyPublisher<TemplateUserProfilePresence, Never> {
$presence.eraseToAnyPublisher()
}
let userId: String = "123"
let displayName: String? = "Alice"
let avatarUrl: String? = "mx123@matrix.com"
let currentlyActive: Bool = true
let lastActive: UInt = 1630596918513
}
@@ -0,0 +1,36 @@
//
// 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, *)
protocol TemplateUserProfileServiceProtocol: Avatarable {
var userId: String { get }
var displayName: String? { get }
var avatarUrl: String? { get }
var presencePublisher: AnyPublisher<TemplateUserProfilePresence, Never> { get }
}
@available(iOS 14.0, *)
extension TemplateUserProfileServiceProtocol {
var mxContentUri: String? {
avatarUrl
}
var matrixItemId: String {
userId
}
}
@@ -0,0 +1,68 @@
//
// 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 TemplateUserProfile: View {
@Environment(\.theme) var theme: ThemeSwiftUI
@ObservedObject var viewModel: TemplateUserProfileViewModel
var leftButton: some View {
Button(VectorL10n.cancel) {
viewModel.proccess(viewAction: .cancel)
}
}
var rightButton: some View {
Button(VectorL10n.done) {
viewModel.proccess(viewAction: .cancel)
}
}
var body: some View {
VStack {
TemplateUserProfileHeader(
avatar: viewModel.viewState.avatar,
displayName: viewModel.viewState.displayName,
presence: viewModel.viewState.presence
)
Divider()
VStack{
HStack(alignment: .center){
Spacer()
Text("More great user content!")
.font(theme.fonts.title2)
.foregroundColor(theme.colors.secondaryContent)
Spacer()
}
}
.frame(maxHeight: .infinity)
}
.frame(maxHeight: .infinity)
.navigationTitle(viewModel.viewState.displayName ?? "")
.navigationBarItems(leading: leftButton, trailing: rightButton)
}
}
@available(iOS 14.0, *)
struct TemplateUserProfile_Previews: PreviewProvider {
static var previews: some View {
TemplateUserProfile(viewModel: TemplateUserProfileViewModel(userService: MockTemplateUserProfileService.example))
.addDependency(MockAvatarService.example)
}
}
@@ -0,0 +1,52 @@
//
// 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 TemplateUserProfileHeader: View {
@Environment(\.theme) var theme: ThemeSwiftUI
let avatar: AvatarInputType?
let displayName: String?
let presence: TemplateUserProfilePresence
var body: some View {
VStack {
if let avatar = avatar {
HStack{
Spacer()
AvatarImage(avatarData: avatar, size: .xxLarge)
Spacer()
}
.padding(.vertical)
}
VStack(spacing: 8){
Text(displayName ?? "")
.font(theme.fonts.title3)
TemplateUserProfilePresenceView(presense: presence)
}
}
}
}
@available(iOS 14.0, *)
struct TemplateUserProfileHeader_Previews: PreviewProvider {
static var previews: some View {
TemplateUserProfileHeader(avatar: MockAvatarInput.example, displayName: "Alice", presence: .online)
.addDependency(MockAvatarService.example)
}
}
@@ -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 14.0, *)
struct TemplateUserProfilePresenceView: View {
let presense: TemplateUserProfilePresence
var foregroundColor: Color {
switch presense {
case .online:
return .green
case .idle:
return .orange
case .offline:
return .gray
}
}
var body: some View {
HStack {
Image(systemName: "circle.fill")
.resizable()
.frame(width: 8, height: 8)
.foregroundColor(foregroundColor)
Text(presense.title)
.font(.subheadline)
}
.foregroundColor(foregroundColor)
.padding(0)
}
}
@available(iOS 14.0, *)
struct TemplatePresenceView_Previews: PreviewProvider {
static var previews: some View {
VStack(alignment:.leading){
Text("Presence")
ForEach(TemplateUserProfilePresence.allCases) { presence in
TemplateUserProfilePresenceView(presense: presence)
}
}
}
}
@@ -0,0 +1,87 @@
//
// 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 Combine
@available(iOS 14.0, *)
class TemplateUserProfileViewModel: ObservableObject, TemplateUserProfileViewModelProtocol {
// MARK: - Properties
// MARK: Private
private let userService: TemplateUserProfileServiceProtocol
private var cancellables = Set<AnyCancellable>()
// MARK: Public
@Published private(set) var viewState: TemplateUserProfileViewState
var completion: ((TemplateUserProfileViewModelResult) -> Void)?
private static func defaultState(userService: TemplateUserProfileServiceProtocol) -> TemplateUserProfileViewState {
return TemplateUserProfileViewState(avatar: userService.avatarData, displayName: userService.displayName)
}
// MARK: - Setup
init(userService: TemplateUserProfileServiceProtocol, initialState: TemplateUserProfileViewState? = nil) {
self.userService = userService
self.viewState = initialState ?? Self.defaultState(userService: userService)
userService.presencePublisher
.map(TemplateUserProfileStateAction.updatePresence)
.receive(on: DispatchQueue.main)
.sink(receiveValue: self.dispatch(action:))
.store(in: &cancellables)
}
// MARK: - Public methods
func proccess(viewAction: TemplateUserProfileViewAction) {
switch viewAction {
case .cancel:
self.cancel()
case .done:
self.done()
}
}
// MARK: - Private methods
/**
Send state actions to mutate the state.
*/
private func dispatch(action: TemplateUserProfileStateAction) {
var newState = self.viewState
reducer(state: &newState, action: action)
self.viewState = newState
}
/**
A redux style reducer, all modifications to state happen here. Recieves a state and a state action and produces a new state.
*/
private func reducer(state: inout TemplateUserProfileViewState, action: TemplateUserProfileStateAction) {
switch action {
case .updatePresence(let presence):
state.presence = presence
}
}
private func done() {
completion?(.done)
}
private func cancel() {
completion?(.cancel)
}
}
@@ -0,0 +1,21 @@
//
// 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 TemplateUserProfileViewModelProtocol {
var completion: ((TemplateUserProfileViewModelResult) -> Void)? { get set }
}