Files
letro-ios/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift
Doug 653dc30b4a #42: Proof of concept login via OIDC.
Uses AppAuth library in the app, ignoring token refresh
2022-07-28 09:28:42 +01:00

179 lines
6.3 KiB
Swift

//
// 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 Combine
import SwiftUI
typealias HomeScreenViewModelType = StateStoreViewModel<HomeScreenViewState, HomeScreenViewAction>
class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol {
private let attributedStringBuilder: AttributedStringBuilderProtocol
private var roomUpdateListeners = Set<AnyCancellable>()
private var roomsUpdateTask: Task<Void, Never>? {
willSet {
roomsUpdateTask?.cancel()
}
}
private var roomSummaries: [RoomSummaryProtocol]? {
didSet {
state.isLoadingRooms = (roomSummaries?.count ?? 0 == 0)
}
}
var callback: ((HomeScreenViewModelAction) -> Void)?
// MARK: - Setup
init(initialDisplayName: String, attributedStringBuilder: AttributedStringBuilderProtocol) {
self.attributedStringBuilder = attributedStringBuilder
super.init(initialViewState: HomeScreenViewState(userDisplayName: initialDisplayName,
isLoadingRooms: true))
}
// MARK: - Public
override func process(viewAction: HomeScreenViewAction) async {
switch viewAction {
case .loadRoomData(let roomIdentifier):
loadRoomDataForIdentifier(roomIdentifier)
case .selectRoom(let roomIdentifier):
callback?(.selectRoom(roomIdentifier: roomIdentifier))
case .tapUserAvatar:
callback?(.tapUserAvatar)
case .verifySession:
callback?(.verifySession)
}
}
func updateWithRoomSummaries(_ roomSummaries: [RoomSummaryProtocol]) {
roomsUpdateTask = Task {
await updateWithRoomSummaries(roomSummaries)
}
}
private func updateWithRoomSummaries(_ roomSummaries: [RoomSummaryProtocol]) async {
var rooms = [HomeScreenRoom]()
for summary in roomSummaries {
if Task.isCancelled {
return
}
rooms.append(await buildOrUpdateRoomForSummary(summary))
}
if Task.isCancelled {
return
}
state.rooms = rooms
self.roomSummaries = roomSummaries
roomUpdateListeners.removeAll()
roomSummaries.forEach { roomSummary in
roomSummary.callbacks
.receive(on: DispatchQueue.main)
.sink { [weak self] callback in
guard let self = self else {
return
}
Task {
if let index = self.state.rooms.firstIndex(where: { $0.id == roomSummary.id }) {
switch callback {
case .updatedLastMessage:
var room = self.state.rooms[index]
room.lastMessage = await self.lastMessageFromEventBrief(roomSummary.lastMessage)
self.state.rooms[index] = room
default:
self.state.rooms[index] = await self.buildOrUpdateRoomForSummary(roomSummary)
}
}
}
}
.store(in: &roomUpdateListeners)
}
}
func updateWithUserAvatar(_ avatar: UIImage) {
state.userAvatar = avatar
}
func updateWithUserDisplayName(_ displayName: String) {
state.userDisplayName = displayName
}
func showSessionVerificationBanner() {
state.showSessionVerificationBanner = true
}
func hideSessionVerificationBanner() {
state.showSessionVerificationBanner = false
}
// MARK: - Private
private func loadRoomDataForIdentifier(_ roomIdentifier: String) {
guard let roomSummary = roomSummaries?.first(where: { $0.id == roomIdentifier }) else {
MXLog.error("Invalid room identifier")
return
}
Task { await roomSummary.loadDetails() }
}
private func buildOrUpdateRoomForSummary(_ roomSummary: RoomSummaryProtocol) async -> HomeScreenRoom {
guard var room = state.rooms.first(where: { $0.id == roomSummary.id }) else {
return HomeScreenRoom(id: roomSummary.id,
displayName: roomSummary.displayName,
topic: roomSummary.topic,
lastMessage: await lastMessageFromEventBrief(roomSummary.lastMessage),
avatar: roomSummary.avatar,
isDirect: roomSummary.isDirect,
isEncrypted: roomSummary.isEncrypted,
isSpace: roomSummary.isSpace,
isTombstoned: roomSummary.isTombstoned)
}
room.avatar = roomSummary.avatar
room.displayName = roomSummary.displayName
room.topic = roomSummary.topic
return room
}
private func lastMessageFromEventBrief(_ eventBrief: EventBrief?) async -> String? {
guard let eventBrief = eventBrief else {
return nil
}
let senderDisplayName = senderDisplayNameForBrief(eventBrief)
if let htmlBody = eventBrief.htmlBody,
let lastMessageAttributedString = await attributedStringBuilder.fromHTML(htmlBody) {
return "\(senderDisplayName): \(String(lastMessageAttributedString.characters))"
} else {
return "\(senderDisplayName): \(eventBrief.body)"
}
}
private func senderDisplayNameForBrief(_ brief: EventBrief) -> String {
brief.senderDisplayName ?? brief.senderId
}
}