Loading images and user avatars.

This commit is contained in:
Stefan Ceriu
2022-03-17 18:09:29 +02:00
parent 30378ae6a9
commit 120bcaf012
31 changed files with 542 additions and 207 deletions
@@ -16,10 +16,10 @@
import SwiftUI
import Combine
import Kingfisher
struct HomeScreenCoordinatorParameters {
let userSession: UserSession
let mediaProvider: MediaProviderProtocol
}
enum HomeScreenCoordinatorResult {
@@ -47,11 +47,11 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
// MARK: - Setup
init(parameters: HomeScreenCoordinatorParameters, imageCache: Kingfisher.ImageCache) {
init(parameters: HomeScreenCoordinatorParameters) {
self.parameters = parameters
let userDisplayName = self.parameters.userSession.userDisplayName ?? self.parameters.userSession.userIdentifier
viewModel = HomeScreenViewModel(userDisplayName: userDisplayName, imageCache: imageCache)
viewModel = HomeScreenViewModel(userDisplayName: userDisplayName, mediaProvider: self.parameters.mediaProvider)
let view = HomeScreen(context: viewModel.context)
hostingController = UIHostingController(rootView: view)
@@ -63,7 +63,7 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
case .logout:
self.completion?(.logout)
case .loadUserAvatar:
self.parameters.userSession.loadUserAvatar({ result in
self.parameters.mediaProvider.loadCurrentUserAvatar({ result in
switch result {
case .success(let avatar):
self.viewModel.updateWithUserAvatar(avatar)
@@ -16,7 +16,6 @@
import SwiftUI
import Combine
import Kingfisher
@available(iOS 14, *)
typealias HomeScreenViewModelType = StateStoreViewModel<HomeScreenViewState,
@@ -25,21 +24,21 @@ typealias HomeScreenViewModelType = StateStoreViewModel<HomeScreenViewState,
@available(iOS 14, *)
class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol {
private let mediaProvider: MediaProviderProtocol
private var roomUpdateListeners = Set<AnyCancellable>()
private var roomList: [RoomProxyProtocol]? {
didSet {
self.state.isLoadingRooms = (roomList?.count ?? 0 == 0)
}
}
private let imageCache: ImageCache
var completion: ((HomeScreenViewModelResult) -> Void)?
// MARK: - Setup
init(userDisplayName: String, imageCache: Kingfisher.ImageCache) {
self.imageCache = imageCache
init(userDisplayName: String, mediaProvider: MediaProviderProtocol) {
self.mediaProvider = mediaProvider
super.init(initialViewState: HomeScreenViewState(userDisplayName: userDisplayName, isLoadingRooms: true))
}
@@ -94,37 +93,14 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
private func loadAvatarForRoomWithIdentifier(_ roomIdentifier: String) {
guard let room = roomList?.filter({ $0.id == roomIdentifier }).first,
let cacheKey = room.avatarURL?.path else {
let avatarURLString = room.avatarURL 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
mediaProvider.loadImageFromURL(avatarURLString) { [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
if case let .success(image) = result {
self.updateAvatar(image, forRoomWithIdentifier: roomIdentifier)
}
}
}
@@ -15,7 +15,6 @@
//
import SwiftUI
import Kingfisher
struct HomeScreen: View {
@@ -154,7 +153,8 @@ struct RoomCell: View {
struct HomeScreen_Previews: PreviewProvider {
static var previews: some View {
let viewModel = HomeScreenViewModel(userDisplayName: "Johnny Appleseed", imageCache: ImageCache.default)
let viewModel = HomeScreenViewModel(userDisplayName: "Johnny Appleseed",
mediaProvider: MockMediaProvider())
let rooms = [MockRoomProxy(displayName: "Alfa"),
MockRoomProxy(displayName: "Beta"),
@@ -18,6 +18,7 @@ import SwiftUI
struct RoomScreenCoordinatorParameters {
let roomProxy: RoomProxyProtocol
let mediaProvider: MediaProviderProtocol
}
final class RoomScreenCoordinator: Coordinator, Presentable {
@@ -43,10 +44,12 @@ final class RoomScreenCoordinator: Coordinator, Presentable {
let timelineProvider = RoomTimelineProvider(roomProxy: parameters.roomProxy)
let timelineController = RoomTimelineController(timelineProvider: timelineProvider,
timelineItemFactory: TimelineItemFactory(),
timelineViewFactory: TimelineViewFactory())
timelineItemFactory: RoomTimelineItemFactory(mediaProvider: parameters.mediaProvider),
mediaProvider: parameters.mediaProvider)
let viewModel = RoomScreenViewModel(roomProxy: parameters.roomProxy, timelineController: timelineController)
let viewModel = RoomScreenViewModel(roomProxy: parameters.roomProxy,
timelineController: timelineController,
timelineViewFactory: RoomTimelineViewFactory())
let view = RoomScreen(context: viewModel.context)
roomScreenViewModel = viewModel
roomScreenHostingController = UIHostingController(rootView: view)
@@ -28,6 +28,6 @@ enum RoomScreenViewAction {
struct RoomScreenViewState: BindableState {
var roomTitle: String = ""
var timelineItems: [RoomTimelineViewProvider] = []
var items: [RoomTimelineViewProvider] = []
var isBackPaginating = false
}
@@ -29,24 +29,35 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
private let roomProxy: RoomProxyProtocol
private let timelineController: RoomTimelineControllerProtocol
private let timelineViewFactory: RoomTimelineViewFactory
// MARK: - Setup
init(roomProxy: RoomProxyProtocol, timelineController: RoomTimelineControllerProtocol) {
init(roomProxy: RoomProxyProtocol,
timelineController: RoomTimelineControllerProtocol,
timelineViewFactory: RoomTimelineViewFactory) {
self.roomProxy = roomProxy
self.timelineController = timelineController
self.timelineViewFactory = timelineViewFactory
super.init(initialViewState: RoomScreenViewState())
state.roomTitle = roomProxy.name ?? ""
state.timelineItems = timelineController.timelineItems
buildTimelineViews()
timelineController.callbacks.sink { [weak self] callback in
guard let self = self else { return }
switch callback {
case .updatedTimelineItems:
self.state.timelineItems = timelineController.timelineItems
self.buildTimelineViews()
case .updatedTimelineItem(let itemId):
guard let timelineItem = self.timelineController.timelineItems.first(where: { $0.id == itemId }),
let viewIndex = self.state.items.firstIndex(where: { $0.id == itemId }) else {
return
}
self.state.items[viewIndex] = timelineViewFactory.buildTimelineViewFor(timelineItem)
}
}.store(in: &cancellables)
}
@@ -60,10 +71,18 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
timelineController.paginateBackwards(Constants.backPaginationPageSize) { [weak self] _ in
self?.state.isBackPaginating = false
}
case .itemAppeared:
break
case .itemDisappeared:
break
case .itemAppeared(let id):
timelineController.processItemAppearance(id)
case .itemDisappeared(let id):
timelineController.processItemDisappearance(id)
}
}
// MARK: - Private
private func buildTimelineViews() {
state.items = timelineController.timelineItems.map { item in
timelineViewFactory.buildTimelineViewFor(item)
}
}
}
@@ -40,12 +40,9 @@ struct RoomScreen: View {
}
// No idea why previews don't work otherwise
ForEach(isPreview ? context.viewState.timelineItems : timelineItems) { timelineItem in
ForEach(isPreview ? context.viewState.items : timelineItems) { timelineItem in
timelineItem
.listRowSeparator(.hidden)
.task {
}
.onAppear {
context.send(viewAction: .itemAppeared(id: timelineItem.id))
}
@@ -75,7 +72,7 @@ struct RoomScreen: View {
attemptBackPagination()
})
.onChange(of: context.viewState.timelineItems) { _ in
.onChange(of: context.viewState.items) { _ in
// Don't update the list while moving
if tableViewObserver.isDecelerating || tableViewObserver.isTracking {
hasPendingChanges = true
@@ -83,7 +80,7 @@ struct RoomScreen: View {
}
tableViewObserver.saveCurrentOffset()
timelineItems = context.viewState.timelineItems
timelineItems = context.viewState.items
}
.onReceive(tableViewObserver.scrollViewDidRest, perform: {
if hasPendingChanges == false {
@@ -91,7 +88,7 @@ struct RoomScreen: View {
}
tableViewObserver.saveCurrentOffset()
timelineItems = context.viewState.timelineItems
timelineItems = context.viewState.items
hasPendingChanges = false
})
.onChange(of: timelineItems, perform: { _ in
@@ -244,7 +241,8 @@ private class TableViewObserver: NSObject, UITableViewDelegate {
struct RoomScreen_Previews: PreviewProvider {
static var previews: some View {
let viewModel = RoomScreenViewModel(roomProxy: MockRoomProxy(displayName: "Test"),
timelineController: MockRoomTimelineController())
timelineController: MockRoomTimelineController(),
timelineViewFactory: RoomTimelineViewFactory())
RoomScreen(context: viewModel.context)
}