mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Loading images and user avatars.
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user