Added room avatar caching and room list loading state.
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Kingfisher
|
||||
|
||||
class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
private let window: UIWindow
|
||||
@@ -76,7 +77,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
}
|
||||
|
||||
let parameters = HomeScreenCoordinatorParameters(userSession: userSession)
|
||||
let coordinator = HomeScreenCoordinator(parameters: parameters)
|
||||
let coordinator = HomeScreenCoordinator(parameters: parameters, imageCache: ImageCache.default)
|
||||
|
||||
coordinator.completion = { [weak self] result in
|
||||
switch result {
|
||||
|
||||
@@ -50,7 +50,7 @@ class UserSession: ClientDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func getUserAvatar(_ completion: @escaping (Result<UIImage?, Error>) -> Void) {
|
||||
func loadUserAvatar(_ completion: @escaping (Result<UIImage?, Error>) -> Void) {
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
do {
|
||||
let avatarData = try self.client.avatar()
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
import Kingfisher
|
||||
|
||||
struct HomeScreenCoordinatorParameters {
|
||||
let userSession: UserSession
|
||||
@@ -45,11 +46,11 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(parameters: HomeScreenCoordinatorParameters) {
|
||||
init(parameters: HomeScreenCoordinatorParameters, imageCache: Kingfisher.ImageCache) {
|
||||
self.parameters = parameters
|
||||
|
||||
let userDisplayName = self.parameters.userSession.userDisplayName ?? self.parameters.userSession.userIdentifier
|
||||
viewModel = HomeScreenViewModel(userDisplayName: userDisplayName)
|
||||
viewModel = HomeScreenViewModel(userDisplayName: userDisplayName, imageCache: imageCache)
|
||||
|
||||
let view = HomeScreen(context: viewModel.context)
|
||||
hostingController = UIHostingController(rootView: view)
|
||||
@@ -61,7 +62,7 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
|
||||
case .logout:
|
||||
self.completion?(.logout)
|
||||
case .loadUserAvatar:
|
||||
self.parameters.userSession.getUserAvatar({ result in
|
||||
self.parameters.userSession.loadUserAvatar({ result in
|
||||
switch result {
|
||||
case .success(let avatar):
|
||||
self.viewModel.updateWithUserAvatar(avatar)
|
||||
|
||||
@@ -33,6 +33,7 @@ struct HomeScreenViewState: BindableState {
|
||||
var userAvatar: UIImage?
|
||||
|
||||
var rooms: [HomeScreenRoom] = []
|
||||
var isLoadingRooms: Bool = false
|
||||
|
||||
var directRooms: [HomeScreenRoom] {
|
||||
rooms.filter { $0.isDirect }
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
@available(iOS 14, *)
|
||||
typealias HomeScreenViewModelType = StateStoreViewModel<HomeScreenViewState,
|
||||
@@ -27,7 +28,12 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var roomList: [RoomModelProtocol]?
|
||||
private var roomList: [RoomModelProtocol]? {
|
||||
didSet {
|
||||
self.state.isLoadingRooms = (roomList == nil)
|
||||
}
|
||||
}
|
||||
private let imageCache: ImageCache
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@@ -35,8 +41,9 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(userDisplayName: String) {
|
||||
super.init(initialViewState: HomeScreenViewState(userDisplayName: userDisplayName))
|
||||
init(userDisplayName: String, imageCache: Kingfisher.ImageCache) {
|
||||
self.imageCache = imageCache
|
||||
super.init(initialViewState: HomeScreenViewState(userDisplayName: userDisplayName, isLoadingRooms: true))
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
@@ -46,24 +53,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
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
|
||||
}
|
||||
}
|
||||
self.loadAvatarForRoomWithIdentifier(roomId)
|
||||
case .loadUserAvatar:
|
||||
self.completion?(.loadUserAvatar)
|
||||
}
|
||||
@@ -84,4 +74,51 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
func updateWithUserAvatar(_ avatar: UIImage?) {
|
||||
self.state.userAvatar = avatar
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func loadAvatarForRoomWithIdentifier(_ roomIdentifier: String) {
|
||||
guard let room = roomList?.filter({ $0.identifier == roomIdentifier }).first,
|
||||
let cacheKey = room.avatarURL?.path else {
|
||||
return
|
||||
}
|
||||
|
||||
if imageCache.isCached(forKey: cacheKey) {
|
||||
imageCache.retrieveImage(forKey: cacheKey) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
switch result {
|
||||
case .success(let value):
|
||||
self.updateAvatar(value.image, forRoomWithIdentifier: roomIdentifier)
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed retrieving avatar from cache with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
room.loadAvatar { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch result {
|
||||
case .success(let avatar):
|
||||
guard let avatar = avatar else {
|
||||
return
|
||||
}
|
||||
|
||||
self.imageCache.store(avatar, forKey: cacheKey)
|
||||
self.updateAvatar(avatar, forRoomWithIdentifier: roomIdentifier)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateAvatar(_ avatar: UIImage?, forRoomWithIdentifier roomIdentifier: String) {
|
||||
guard let index = self.state.rooms.firstIndex(where: { $0.id == roomIdentifier }) else {
|
||||
return
|
||||
}
|
||||
|
||||
self.state.rooms[index].avatar = avatar
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct HomeScreen: View {
|
||||
|
||||
@@ -31,6 +32,7 @@ struct HomeScreen: View {
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 40, height: 40, alignment: .center)
|
||||
.mask(Circle())
|
||||
} else {
|
||||
let _ = context.send(viewAction: .loadUserAvatar)
|
||||
}
|
||||
@@ -40,21 +42,30 @@ struct HomeScreen: View {
|
||||
}
|
||||
.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)
|
||||
if context.viewState.isLoadingRooms {
|
||||
VStack {
|
||||
Text("Loading rooms")
|
||||
ProgressView()
|
||||
}
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
.headerProminence(.increased)
|
||||
.listStyle(.plain)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
@@ -81,6 +92,7 @@ struct RoomCell: View {
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 40, height: 40)
|
||||
.mask(Circle())
|
||||
} else {
|
||||
let _ = context.send(viewAction: .loadRoomAvatar(roomId: room.id))
|
||||
Image(systemName: "person.3")
|
||||
@@ -119,7 +131,7 @@ struct RoomCell: View {
|
||||
|
||||
struct HomeScreen_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let viewModel = HomeScreenViewModel(userDisplayName: "Johnny Appleseed")
|
||||
let viewModel = HomeScreenViewModel(userDisplayName: "Johnny Appleseed", imageCache: ImageCache.default)
|
||||
|
||||
let rooms = [MockRoomModel(displayName: "Alfa"),
|
||||
MockRoomModel(displayName: "Beta"),
|
||||
@@ -127,6 +139,8 @@ struct HomeScreen_Previews: PreviewProvider {
|
||||
|
||||
viewModel.updateWithRoomList(rooms)
|
||||
|
||||
viewModel.updateWithUserAvatar(UIImage(systemName: "person.fill.questionmark"))
|
||||
|
||||
return HomeScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ struct MockRoomModel: RoomModelProtocol {
|
||||
let isPublic = Bool.random()
|
||||
let isEncrypted = Bool.random()
|
||||
|
||||
func getAvatar(_ completion: (Result<UIImage?, Error>) -> Void) {
|
||||
func loadAvatar(_ completion: (Result<UIImage?, Error>) -> Void) {
|
||||
completion(.success(UIImage(systemName: "wand.and.stars")))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ struct RoomModel: RoomModelProtocol {
|
||||
return URL(string: urlString)
|
||||
}
|
||||
|
||||
func getAvatar(_ completion: @escaping (Result<UIImage?, Error>) -> Void) {
|
||||
func loadAvatar(_ completion: @escaping (Result<UIImage?, Error>) -> Void) {
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
do {
|
||||
let avatarData = try room.avatar()
|
||||
|
||||
@@ -22,5 +22,5 @@ protocol RoomModelProtocol {
|
||||
|
||||
var avatarURL: URL? { get }
|
||||
|
||||
func getAvatar(_ completion: @escaping (Result<UIImage?, Error>) -> Void)
|
||||
func loadAvatar(_ completion: @escaping (Result<UIImage?, Error>) -> Void)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user