mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 13:37:11 +00:00
Added logout option, room list on home screen, made various methods asynchronous,
This commit is contained in:
parent
d90738e1e4
commit
b53efb4869
@ -23,7 +23,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
init() {
|
||||
splashViewController = SplashViewController()
|
||||
mainNavigationController = UINavigationController(rootViewController: splashViewController)
|
||||
mainNavigationController.setNavigationBarHidden(true, animated: false)
|
||||
mainNavigationController.navigationBar.isHidden = true
|
||||
window = UIWindow(frame: UIScreen.main.bounds)
|
||||
window.rootViewController = mainNavigationController
|
||||
|
||||
@ -46,6 +46,10 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
|
||||
// MARK: - AuthenticationCoordinatorDelegate
|
||||
|
||||
func authenticationCoordinatorDidStartLoading(_ authenticationCoordinator: AuthenticationCoordinator) {
|
||||
|
||||
}
|
||||
|
||||
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator, didFailWithError error: AuthenticationCoordinatorError) {
|
||||
|
||||
}
|
||||
@ -55,7 +59,8 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
}
|
||||
|
||||
func authenticationCoordinatorDidTearDownUserSession(_ authenticationCoordinator: AuthenticationCoordinator) {
|
||||
|
||||
mainNavigationController.setViewControllers([splashViewController], animated: false)
|
||||
authenticationCoordinator.start()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
@ -68,6 +73,13 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
let parameters = HomeScreenCoordinatorParameters(userSession: userSession)
|
||||
let coordinator = HomeScreenCoordinator(parameters: parameters)
|
||||
|
||||
coordinator.completion = { [weak self] result in
|
||||
switch result {
|
||||
case .logout:
|
||||
self?.authenticationCoordinator.logout()
|
||||
}
|
||||
}
|
||||
|
||||
add(childCoordinator: coordinator)
|
||||
navigationRouter.setRootModule(coordinator)
|
||||
}
|
||||
|
@ -15,6 +15,9 @@ enum AuthenticationCoordinatorError: Error {
|
||||
}
|
||||
|
||||
protocol AuthenticationCoordinatorDelegate: AnyObject {
|
||||
|
||||
func authenticationCoordinatorDidStartLoading(_ authenticationCoordinator: AuthenticationCoordinator)
|
||||
|
||||
func authenticationCoordinatorDidSetupUserSession(_ authenticationCoordinator: AuthenticationCoordinator)
|
||||
|
||||
func authenticationCoordinatorDidTearDownUserSession(_ authenticationCoordinator: AuthenticationCoordinator)
|
||||
@ -40,19 +43,46 @@ class AuthenticationCoordinator: Coordinator {
|
||||
}
|
||||
|
||||
func start() {
|
||||
|
||||
delegate?.authenticationCoordinatorDidStartLoading(self)
|
||||
|
||||
let availableRestoreTokens = keychainController.restoreTokens()
|
||||
|
||||
guard let usernameTokenTuple = availableRestoreTokens.first else {
|
||||
startNewLoginFlow()
|
||||
startNewLoginFlow { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.delegate?.authenticationCoordinatorDidSetupUserSession(self)
|
||||
case .failure(let error):
|
||||
self.delegate?.authenticationCoordinator(self, didFailWithError: error)
|
||||
MXLog.error("Failed logging in user with error: \(error)")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
restorePreviousLogin(usernameTokenTuple)
|
||||
restorePreviousLogin(usernameTokenTuple) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch result {
|
||||
case .success:
|
||||
self.delegate?.authenticationCoordinatorDidSetupUserSession(self)
|
||||
case .failure(let error):
|
||||
self.delegate?.authenticationCoordinator(self, didFailWithError: error)
|
||||
MXLog.error("Failed restoring login with error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func logout() {
|
||||
keychainController.removeAllTokens()
|
||||
userSession = nil
|
||||
delegate?.authenticationCoordinatorDidTearDownUserSession(self)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func startNewLoginFlow() {
|
||||
private func startNewLoginFlow(_ completion: @escaping (Result<(), AuthenticationCoordinatorError>) -> Void) {
|
||||
let parameters = LoginScreenCoordinatorParameters()
|
||||
let coordinator = LoginScreenCoordinator(parameters: parameters)
|
||||
|
||||
@ -63,16 +93,18 @@ class AuthenticationCoordinator: Coordinator {
|
||||
|
||||
switch result {
|
||||
case .login(let result):
|
||||
do {
|
||||
self.setupUserSessionForClient(try loginNewClient(basePath: self.baseDirectoryPathForUsername(result.username),
|
||||
username: result.username,
|
||||
password: result.password))
|
||||
self.login(username: result.username, password: result.password) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
|
||||
self.remove(childCoordinator: coordinator)
|
||||
self.navigationRouter.dismissModule()
|
||||
} catch {
|
||||
self.delegate?.authenticationCoordinator(self, didFailWithError: .failedLoggingIn)
|
||||
MXLog.error("Failed logging in user with error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -83,13 +115,42 @@ class AuthenticationCoordinator: Coordinator {
|
||||
coordinator.start()
|
||||
}
|
||||
|
||||
private func restorePreviousLogin(_ usernameTokenTuple: (username: String, token: String)) {
|
||||
do {
|
||||
setupUserSessionForClient(try loginWithToken(basePath: baseDirectoryPathForUsername(usernameTokenTuple.username),
|
||||
restoreToken: usernameTokenTuple.token))
|
||||
} catch {
|
||||
delegate?.authenticationCoordinator(self, didFailWithError: .failedRestoringLogin)
|
||||
MXLog.error("Failed restoring login with error: \(error)")
|
||||
private func login(username: String, password: String, completion: @escaping (Result<Void, AuthenticationCoordinatorError>) -> Void) {
|
||||
DispatchQueue.global(qos: .background).async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
do {
|
||||
self.setupUserSessionForClient(try loginNewClient(basePath: self.baseDirectoryPathForUsername(username),
|
||||
username: username,
|
||||
password: password))
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(()))
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(.failedLoggingIn))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func restorePreviousLogin(_ usernameTokenTuple: (username: String, token: String), completion: @escaping (Result<Void, AuthenticationCoordinatorError>) -> Void) {
|
||||
DispatchQueue.global(qos: .background).async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
do {
|
||||
self.setupUserSessionForClient(try loginWithToken(basePath: self.baseDirectoryPathForUsername(usernameTokenTuple.username),
|
||||
restoreToken: usernameTokenTuple.token))
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(()))
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(.failedRestoringLogin))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +167,6 @@ class AuthenticationCoordinator: Coordinator {
|
||||
}
|
||||
|
||||
userSession = UserSession(client: client)
|
||||
delegate?.authenticationCoordinatorDidSetupUserSession(self)
|
||||
}
|
||||
|
||||
private func baseDirectoryPathForUsername(_ username: String) -> String {
|
||||
|
@ -38,6 +38,7 @@ struct LoginScreen: View {
|
||||
.padding(.horizontal, 8.0)
|
||||
.navigationTitle("Login")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,33 +7,41 @@
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
import Combine
|
||||
import UIKit
|
||||
|
||||
class UserSession {
|
||||
enum UserSessionCallback {
|
||||
case updatedData
|
||||
}
|
||||
|
||||
enum UserSessionError: Error {
|
||||
case failedRetrievingAvatar
|
||||
}
|
||||
|
||||
class UserSession: ClientDelegate {
|
||||
|
||||
private let client: Client
|
||||
|
||||
let callbacks = PassthroughSubject<UserSessionCallback, Never>()
|
||||
|
||||
init(client: Client) {
|
||||
self.client = client
|
||||
|
||||
if !client.hasFirstSynced() {
|
||||
MXLog.info("Started initial sync")
|
||||
client.startSync()
|
||||
MXLog.info("Finished intial sync")
|
||||
client.startSync(delegate: self)
|
||||
}
|
||||
}
|
||||
|
||||
func roomList() -> [RoomModel] {
|
||||
client.conversations().compactMap { room in
|
||||
do {
|
||||
return RoomModel(displayName: try room.displayName())
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving room info with error: \(error)")
|
||||
return nil
|
||||
}
|
||||
var userIdentifier: String {
|
||||
do {
|
||||
return try client.userId()
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving room info with error: \(error)")
|
||||
return "Unknown user identifier"
|
||||
}
|
||||
}
|
||||
|
||||
var displayName: String? {
|
||||
var userDisplayName: String? {
|
||||
do {
|
||||
return try client.displayName()
|
||||
} catch {
|
||||
@ -41,4 +49,42 @@ class UserSession {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getUserAvatar(_ completion: @escaping (Result<UIImage?, Error>) -> Void) {
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
do {
|
||||
let avatarData = try self.client.avatar()
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(UIImage(data: Data(bytes: avatarData, count: avatarData.count))))
|
||||
}
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving room name with error: \(error)")
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(UserSessionError.failedRetrievingAvatar))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getRoomList(_ completion: @escaping ([RoomModel]) -> Void) {
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
let conversations = self.client.conversations()
|
||||
|
||||
let rooms = conversations.map {
|
||||
return RoomModel(room: $0)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion(rooms)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: ClientDelegate
|
||||
|
||||
func didReceiveSyncUpdate() {
|
||||
DispatchQueue.main.async {
|
||||
self.callbacks.send(.updatedData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,16 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
struct HomeScreenCoordinatorParameters {
|
||||
let userSession: UserSession
|
||||
}
|
||||
|
||||
enum HomeScreenCoordinatorResult {
|
||||
case logout
|
||||
}
|
||||
|
||||
final class HomeScreenCoordinator: Coordinator, Presentable {
|
||||
|
||||
// MARK: - Properties
|
||||
@ -27,30 +32,52 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
|
||||
// MARK: Private
|
||||
|
||||
private let parameters: HomeScreenCoordinatorParameters
|
||||
private let homeScreenHostingController: UIViewController
|
||||
private var homeScreenViewModel: HomeScreenViewModelProtocol
|
||||
private let hostingController: UIViewController
|
||||
private var viewModel: HomeScreenViewModelProtocol
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
var completion: ((HomeScreenViewModelResult) -> Void)?
|
||||
var completion: ((HomeScreenCoordinatorResult) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
init(parameters: HomeScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
let viewModel = HomeScreenViewModel(username: self.parameters.userSession.displayName ?? "💥")
|
||||
let view = HomeScreen(context: viewModel.context)
|
||||
homeScreenViewModel = viewModel
|
||||
homeScreenHostingController = UIHostingController(rootView: view)
|
||||
let userDisplayName = self.parameters.userSession.userDisplayName ?? self.parameters.userSession.userIdentifier
|
||||
viewModel = HomeScreenViewModel(userDisplayName: userDisplayName)
|
||||
|
||||
homeScreenViewModel.completion = { [weak self] result in
|
||||
let view = HomeScreen(context: viewModel.context)
|
||||
hostingController = UIHostingController(rootView: view)
|
||||
|
||||
viewModel.completion = { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
self.completion?(result)
|
||||
|
||||
switch result {
|
||||
case .logout:
|
||||
self.completion?(.logout)
|
||||
case .loadUserAvatar:
|
||||
self.parameters.userSession.getUserAvatar({ result in
|
||||
switch result {
|
||||
case .success(let avatar):
|
||||
self.viewModel.updateWithUserAvatar(avatar)
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
parameters.userSession.callbacks.sink { [weak self] result in
|
||||
switch result {
|
||||
case .updatedData:
|
||||
self?.updateRoomsList()
|
||||
}
|
||||
}.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
@ -59,6 +86,14 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return self.homeScreenHostingController
|
||||
return self.hostingController
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
func updateRoomsList() {
|
||||
parameters.userSession.getRoomList { [weak self] rooms in
|
||||
self?.viewModel.updateWithRoomList(rooms)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,17 +15,43 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
enum HomeScreenViewModelResult {
|
||||
case logout
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
|
||||
struct HomeScreenViewState: BindableState {
|
||||
let username: String
|
||||
case loadUserAvatar
|
||||
}
|
||||
|
||||
enum HomeScreenViewAction {
|
||||
case logout
|
||||
case loadUserAvatar
|
||||
case loadRoomAvatar(roomId: String)
|
||||
}
|
||||
|
||||
struct HomeScreenViewState: BindableState {
|
||||
let userDisplayName: String
|
||||
var userAvatar: UIImage?
|
||||
|
||||
var rooms: [HomeScreenRoom] = []
|
||||
|
||||
var directRooms: [HomeScreenRoom] {
|
||||
rooms.filter { $0.isDirect }
|
||||
}
|
||||
|
||||
var nondirectRooms: [HomeScreenRoom] {
|
||||
rooms.filter { !$0.isDirect }
|
||||
}
|
||||
}
|
||||
|
||||
struct HomeScreenRoom: Identifiable {
|
||||
let id: String
|
||||
let displayName: String
|
||||
|
||||
let topic: String?
|
||||
let lastMessage: String?
|
||||
|
||||
var avatar: UIImage?
|
||||
|
||||
let isDirect: Bool
|
||||
let isEncrypted: Bool
|
||||
}
|
||||
|
@ -18,28 +18,70 @@ import SwiftUI
|
||||
|
||||
@available(iOS 14, *)
|
||||
typealias HomeScreenViewModelType = StateStoreViewModel<HomeScreenViewState,
|
||||
Never,
|
||||
HomeScreenViewAction>
|
||||
Never,
|
||||
HomeScreenViewAction>
|
||||
@available(iOS 14, *)
|
||||
class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol {
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var roomList: [RoomModelProtocol]?
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var completion: ((HomeScreenViewModelResult) -> Void)?
|
||||
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(username: String) {
|
||||
super.init(initialViewState: HomeScreenViewState(username: username))
|
||||
|
||||
init(userDisplayName: String) {
|
||||
super.init(initialViewState: HomeScreenViewState(userDisplayName: userDisplayName))
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
|
||||
override func process(viewAction: HomeScreenViewAction) {
|
||||
|
||||
switch viewAction {
|
||||
case .logout:
|
||||
self.completion?(.logout)
|
||||
case .loadRoomAvatar(let roomId):
|
||||
guard let room = roomList?.filter({ $0.identifier == roomId }).first else {
|
||||
break
|
||||
}
|
||||
|
||||
room.getAvatar { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch result {
|
||||
case .success(let image):
|
||||
guard let index = self.state.rooms.firstIndex(where: { $0.id == roomId }) else {
|
||||
return
|
||||
}
|
||||
|
||||
self.state.rooms[index].avatar = image
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
case .loadUserAvatar:
|
||||
self.completion?(.loadUserAvatar)
|
||||
}
|
||||
}
|
||||
|
||||
func updateWithRoomList(_ roomList: [RoomModelProtocol]) {
|
||||
self.roomList = roomList
|
||||
state.rooms = roomList.map { roomModel in
|
||||
HomeScreenRoom(id: roomModel.identifier,
|
||||
displayName: roomModel.displayName,
|
||||
topic: roomModel.topic,
|
||||
lastMessage: roomModel.lastMessage,
|
||||
isDirect: roomModel.isDirect,
|
||||
isEncrypted: roomModel.isEncrypted)
|
||||
}
|
||||
}
|
||||
|
||||
func updateWithUserAvatar(_ avatar: UIImage?) {
|
||||
self.state.userAvatar = avatar
|
||||
}
|
||||
}
|
||||
|
@ -15,10 +15,14 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
protocol HomeScreenViewModelProtocol {
|
||||
|
||||
var completion: ((HomeScreenViewModelResult) -> Void)? { get set }
|
||||
@available(iOS 14, *)
|
||||
|
||||
var context: HomeScreenViewModelType.Context { get }
|
||||
|
||||
func updateWithRoomList(_ roomList: [RoomModelProtocol])
|
||||
|
||||
func updateWithUserAvatar(_ avatar: UIImage?)
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct HomeScreen: View {
|
||||
|
||||
@ObservedObject var context: HomeScreenViewModel.Context
|
||||
@ -24,18 +23,110 @@ struct HomeScreen: View {
|
||||
// MARK: Views
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Hello, \(context.viewState.username)!")
|
||||
NavigationView {
|
||||
VStack(spacing: 16.0) {
|
||||
HStack {
|
||||
if let avatar = context.viewState.userAvatar {
|
||||
Image(uiImage: avatar)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 40, height: 40, alignment: .center)
|
||||
} else {
|
||||
let _ = context.send(viewAction: .loadUserAvatar)
|
||||
}
|
||||
Text("Hello, \(context.viewState.userDisplayName)!")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.bold)
|
||||
}
|
||||
.padding(.vertical, 32.0)
|
||||
|
||||
List {
|
||||
Section("People") {
|
||||
ForEach(context.viewState.directRooms) { room in
|
||||
RoomCell(room: room, context: context)
|
||||
}
|
||||
}
|
||||
|
||||
Section("Rooms") {
|
||||
ForEach(context.viewState.nondirectRooms) { room in
|
||||
RoomCell(room: room, context: context)
|
||||
}
|
||||
}
|
||||
}
|
||||
.headerProminence(.increased)
|
||||
.listStyle(.plain)
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Logout") {
|
||||
context.send(viewAction: .logout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RoomCell: View {
|
||||
|
||||
let room: HomeScreenRoom
|
||||
let context: HomeScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 16.0) {
|
||||
if let avatar = room.avatar {
|
||||
Image(uiImage: avatar)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 40, height: 40)
|
||||
} else {
|
||||
let _ = context.send(viewAction: .loadRoomAvatar(roomId: room.id))
|
||||
Image(systemName: "person.3")
|
||||
.frame(width: 40, height: 40)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 4.0) {
|
||||
Text(roomName(room))
|
||||
.font(.headline)
|
||||
.fontWeight(.regular)
|
||||
|
||||
if let roomTopic = room.topic, roomTopic.count > 0 {
|
||||
Text(roomTopic)
|
||||
.font(.footnote)
|
||||
.fontWeight(.bold)
|
||||
.lineLimit(1)
|
||||
}
|
||||
|
||||
if let lastMessage = room.lastMessage {
|
||||
Text(lastMessage)
|
||||
.font(.footnote)
|
||||
.fontWeight(.medium)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 60.0)
|
||||
}
|
||||
|
||||
private func roomName(_ room: HomeScreenRoom) -> String {
|
||||
room.displayName + (room.isEncrypted ? "🛡": "")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct HomeScreen_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let viewModel = HomeScreenViewModel(username: "Johnny Appleseed")
|
||||
HomeScreen(context: viewModel.context)
|
||||
let viewModel = HomeScreenViewModel(userDisplayName: "Johnny Appleseed")
|
||||
|
||||
let rooms = [MockRoomModel(displayName: "Alfa"),
|
||||
MockRoomModel(displayName: "Beta"),
|
||||
MockRoomModel(displayName: "Omega")]
|
||||
|
||||
viewModel.updateWithRoomList(rooms)
|
||||
|
||||
return HomeScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
||||
|
29
ElementX/Sources/Modules/Models/MockRoomModel.swift
Normal file
29
ElementX/Sources/Modules/Models/MockRoomModel.swift
Normal file
@ -0,0 +1,29 @@
|
||||
//
|
||||
// MockRoomModel.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 17.02.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
struct MockRoomModel: RoomModelProtocol {
|
||||
let identifier = UUID().uuidString
|
||||
let name: String? = nil
|
||||
let displayName: String
|
||||
|
||||
let topic: String? = nil
|
||||
let lastMessage: String? = "Last message"
|
||||
|
||||
let avatarURL: URL? = nil
|
||||
|
||||
let isDirect = Bool.random()
|
||||
let isSpace = Bool.random()
|
||||
let isPublic = Bool.random()
|
||||
let isEncrypted = Bool.random()
|
||||
|
||||
func getAvatar(_ completion: (Result<UIImage?, Error>) -> Void) {
|
||||
completion(.success(UIImage(systemName: "wand.and.stars")))
|
||||
}
|
||||
}
|
@ -7,7 +7,86 @@
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import MatrixRustSDK
|
||||
|
||||
struct RoomModel {
|
||||
let displayName: String
|
||||
enum RoomModelError: Error {
|
||||
case failedRetrievingAvatar
|
||||
}
|
||||
|
||||
struct RoomModel: RoomModelProtocol {
|
||||
|
||||
private let room: Room
|
||||
|
||||
init(room: Room) {
|
||||
self.room = room
|
||||
}
|
||||
|
||||
var identifier: String {
|
||||
return room.identifier()
|
||||
}
|
||||
|
||||
var isDirect: Bool {
|
||||
return room.isDirect()
|
||||
}
|
||||
|
||||
var isPublic: Bool {
|
||||
return room.isPublic()
|
||||
}
|
||||
|
||||
var isSpace: Bool {
|
||||
return room.isSpace()
|
||||
}
|
||||
|
||||
var isEncrypted: Bool {
|
||||
return room.isEncrypted()
|
||||
}
|
||||
|
||||
var name: String? {
|
||||
return room.name()
|
||||
}
|
||||
|
||||
var displayName: String {
|
||||
do {
|
||||
return try room.displayName()
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving room name with error: \(error)")
|
||||
return "Error"
|
||||
}
|
||||
}
|
||||
|
||||
var topic: String? {
|
||||
return room.topic()
|
||||
}
|
||||
|
||||
var lastMessage: String? {
|
||||
guard let lastMessage = try? room.messages().last else {
|
||||
return "Last message unknown"
|
||||
}
|
||||
|
||||
return "\(lastMessage.sender()): \(lastMessage.content())"
|
||||
}
|
||||
|
||||
var avatarURL: URL? {
|
||||
guard let urlString = room.avatarUrl() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return URL(string: urlString)
|
||||
}
|
||||
|
||||
func getAvatar(_ completion: @escaping (Result<UIImage?, Error>) -> Void) {
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
do {
|
||||
let avatarData = try room.avatar()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(UIImage(data: Data(bytes: avatarData, count: avatarData.count))))
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(RoomModelError.failedRetrievingAvatar))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
26
ElementX/Sources/Modules/Models/RoomModelProtocol.swift
Normal file
26
ElementX/Sources/Modules/Models/RoomModelProtocol.swift
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// RoomModelProtocol.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 17.02.2022.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol RoomModelProtocol {
|
||||
var identifier: String { get }
|
||||
var isDirect: Bool { get }
|
||||
var isPublic: Bool { get }
|
||||
var isSpace: Bool { get }
|
||||
var isEncrypted: Bool { get }
|
||||
|
||||
var displayName: String { get }
|
||||
var name: String? { get }
|
||||
|
||||
var topic: String? { get }
|
||||
var lastMessage: String? { get }
|
||||
|
||||
var avatarURL: URL? { get }
|
||||
|
||||
func getAvatar(_ completion: @escaping (Result<UIImage?, Error>) -> Void)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user