Skip to content

Commit

Permalink
[Fix #12] Updated SubscribedAlarmsViewController to load more than 50…
Browse files Browse the repository at this point in the history
… Alarms.
  • Loading branch information
joerghartmann committed Dec 10, 2024
1 parent 855bc65 commit 5151fd2
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
{
"originHash" : "114fe3659303e1366d25d790640766abfdf6b0fadec03cbd3866ebdb05f5a710",
"pins" : [
{
"identity" : "cumulocity-clients-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Cumulocity-IoT/cumulocity-clients-swift",
"location" : "https://github.com/SoftwareAG/cumulocity-clients-swift",
"state" : {
"branch" : "main",
"revision" : "fd6203c62a8f7ca6b04fdde22f8c472359e1c3ae"
Expand Down Expand Up @@ -37,5 +38,5 @@
}
}
],
"version" : 2
"version" : 3
}
106 changes: 95 additions & 11 deletions ios/AlarmApp/AlarmApp/Controller/SubscribedAlarmsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ import Combine
import CumulocityCoreLibrary
import UIKit

class SubscribedAlarmsViewController: UITableViewController, SubscribedAlarmListReloadDelegate {
private var data = C8yAlarmCollection()
class SubscribedAlarmsViewController: UITableViewController, SubscribedAlarmListReloadDelegate,
UITableViewDataSourcePrefetching
{
private var viewModel = SubscribedAlarmsViewModel()
private var selectedAlarm: C8yAlarm?
private var cancellableSet = Set<AnyCancellable>()

Expand All @@ -28,56 +30,84 @@ class SubscribedAlarmsViewController: UITableViewController, SubscribedAlarmList
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
UITableViewController.prepareForAlarms(with: self.tableView, delegate: openFilterDelegate)
self.tableView.prefetchDataSource = self
self.view.backgroundColor = .clear
reload()
}

func reload() {
self.viewModel = SubscribedAlarmsViewModel()
fetchNextAlarms()
}

private func fetchNextAlarms() {
let filter = SubscribedAlarmFilter.shared
fetchAlarms(byFilter: filter, byDeviceId: filter.resolvedDeviceId)
}

private func fetchAlarms(byFilter filter: AlarmFilter, byDeviceId deviceId: String?) {
let alarmsApi = Cumulocity.Core.shared.alarms.alarmsApi
let publisher = alarmsApi.getAlarmsByFilter(filter: filter, source: deviceId)
let publisher = alarmsApi.getAlarmsByFilter(filter: filter, page: self.viewModel.nextPage(), source: deviceId)
publisher.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { _ in
},
receiveValue: { collection in
self.data = collection
self.tableView.reloadData()
let currentPage = collection.statistics?.currentPage ?? 1
self.viewModel.pageStatistics = collection.statistics
self.viewModel.alarms.append(contentsOf: collection.alarms ?? [])
if currentPage > 1 {
let indexPathsToReload = self.viewModel.calculateIndexPathsToReload(
from: collection.alarms ?? []
)
self.onFetchAlarmsCompleted(with: indexPathsToReload)
} else {
self.onFetchAlarmsCompleted(with: .none)
}
}
)
.store(in: &self.cancellableSet)
}

private func onFetchAlarmsCompleted(with newIndexPathsToReload: [IndexPath]?) {
guard let newIndexPathsToReload = newIndexPathsToReload else {
self.tableView.reloadData()
return
}
let indexPathsToReload = visibleIndexPathsToReload(intersecting: newIndexPathsToReload)
tableView.reloadRows(at: indexPathsToReload, with: .automatic)
}

// MARK: - Table view data source

override func numberOfSections(in tableView: UITableView) -> Int {
let alarmCount = data.alarms?.count ?? 0
tableView.backgroundView?.isHidden = alarmCount > 0
return alarmCount > 0 ? 1 : 0
let hasAlarms = viewModel.currentCount > 0
tableView.backgroundView?.isHidden = hasAlarms
return hasAlarms ? 1 : 0
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
data.alarms?.count ?? 0
self.viewModel.totalCount
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(
withIdentifier: AlarmListItem.identifier,
for: indexPath
) as? AlarmListItem {
cell.bind(with: data.alarms?[indexPath.item])
if isLoadingCell(for: indexPath) {
cell.bind(with: .none)
} else {
cell.bind(with: self.viewModel.alarm(at: indexPath.item))
}
return cell
}
fatalError("Cannot create AlarmListItem")
}

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
self.selectedAlarm = data.alarms?[indexPath.item]
self.selectedAlarm = self.viewModel.alarm(at: indexPath.item)
performSegue(withIdentifier: UIStoryboardSegue.toAlarmDetails, sender: self)
}

Expand All @@ -92,6 +122,12 @@ class SubscribedAlarmsViewController: UITableViewController, SubscribedAlarmList
fatalError("Cannot create ListViewHeaderItem")
}

func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
if indexPaths.contains(where: isLoadingCell) {
fetchNextAlarms()
}
}

// MARK: - Navigation

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
Expand All @@ -103,6 +139,54 @@ class SubscribedAlarmsViewController: UITableViewController, SubscribedAlarmList
}
}

extension SubscribedAlarmsViewController {
/// cell at that index path is beyond the visible alarm count
fileprivate func isLoadingCell(for indexPath: IndexPath) -> Bool {
indexPath.row >= viewModel.currentCount
}

/// alculates the cells of the table view that need to reload when a new page is received
fileprivate func visibleIndexPathsToReload(intersecting indexPaths: [IndexPath]) -> [IndexPath] {
let indexPathsForVisibleRows = tableView.indexPathsForVisibleRows ?? []
let indexPathsIntersection = Set(indexPathsForVisibleRows).intersection(indexPaths)
return Array(indexPathsIntersection)
}
}

final class SubscribedAlarmsViewModel {
var alarms: [C8yAlarm] = []
var pageStatistics: C8yPageStatistics? = C8yPageStatistics()

init() {
}

var totalCount: Int {
pageStatistics?.totalElements ?? 0
}

var currentCount: Int {
alarms.count
}

func alarm(at index: Int) -> C8yAlarm? {
alarms[index]
}

func nextPage() -> Int {
if let currentPage = pageStatistics?.currentPage {
return currentPage + 1
} else {
return 1
}
}

func calculateIndexPathsToReload(from newAlarms: [C8yAlarm]) -> [IndexPath] {
let startIndex = currentCount - newAlarms.count
let endIndex = startIndex + newAlarms.count
return (startIndex..<endIndex).map { IndexPath(row: $0, section: 0) }
}
}

protocol SubscribedAlarmListReloadDelegate: AnyObject {
func reload()
}
15 changes: 13 additions & 2 deletions ios/AlarmApp/AlarmApp/Extensions/AlarmsApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import Combine
import CumulocityCoreLibrary

extension AlarmsApi {
public func getAlarmsByFilter(filter: AlarmFilter, source: String?) -> AnyPublisher<C8yAlarmCollection, Error> {
public func getAlarmsByFilter(filter: AlarmFilter, page: Int? = 1, source: String?) -> AnyPublisher<
C8yAlarmCollection, Error
> {
var severities: [String] = []
for s in filter.severity {
severities.append(s.rawValue)
Expand All @@ -34,6 +36,15 @@ extension AlarmsApi {
types.append(singleAlarmType)
}
}
return self.getAlarms(pageSize: 50, severity: severities, source: source, status: statuses, type: types)
return self.getAlarms(
currentPage: page,
pageSize: 10,
severity: severities,
source: source,
status: statuses,
type: types,
withTotalElements: true,
withTotalPages: true
)
}
}

0 comments on commit 5151fd2

Please sign in to comment.