From ff720fca0df305cd91bd99e39411d21c8b0ab7da Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Thu, 29 Sep 2022 15:44:27 +0300 Subject: [PATCH] feat: initial list view version --- TILogging/Sources/LogEntryTableViewCell.swift | 10 ++ TILogging/Sources/LogsListView.swift | 118 ++++++++++++++++++ TILogging/Sources/LogsStorageViewModel.swift | 80 ++++++++++++ TILogging/TILogging.podspec | 4 + 4 files changed, 212 insertions(+) create mode 100644 TILogging/Sources/LogEntryTableViewCell.swift create mode 100644 TILogging/Sources/LogsListView.swift create mode 100644 TILogging/Sources/LogsStorageViewModel.swift diff --git a/TILogging/Sources/LogEntryTableViewCell.swift b/TILogging/Sources/LogEntryTableViewCell.swift new file mode 100644 index 00000000..729c1f18 --- /dev/null +++ b/TILogging/Sources/LogEntryTableViewCell.swift @@ -0,0 +1,10 @@ +import TIUIKitCore +import TIUIElements +import OSLog + +@available(iOS 15, *) +open class LogEntryTableViewCell: BaseInitializableCell, ConfigurableView { + open func configure(with entry: OSLogEntryLog) { + textLabel?.text = entry.composedMessage + } +} diff --git a/TILogging/Sources/LogsListView.swift b/TILogging/Sources/LogsListView.swift new file mode 100644 index 00000000..c9833c5c --- /dev/null +++ b/TILogging/Sources/LogsListView.swift @@ -0,0 +1,118 @@ +import TIUIKitCore +import OSLog +import UIKit + +@available(iOS 15, *) +open class LogsListView: BaseInitializeableViewController, LogsListViewOutput { + + public enum TableSection: String, Hashable { + case main + } + + private let searchView = UITextField() + private let segmentView = UISegmentedControl() + private let tableView = UITableView() + + lazy private var dataSource = createDataSource() + + public typealias DataSource = UITableViewDiffableDataSource + public typealias Snapshot = NSDiffableDataSourceSnapshot + + public let viewModel = LogsStorageViewModel() + + // MARK: - Life cycle + + open override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + applySnapshot() + } + + open override func addViews() { + super.addViews() + + view.addSubview(searchView) + view.addSubview(segmentView) + view.addSubview(tableView) + } + + open override func configureLayout() { + super.configureLayout() + + [searchView, segmentView, tableView] + .forEach { $0.translatesAutoresizingMaskIntoConstraints = false } + + NSLayoutConstraint.activate([ + searchView.topAnchor.constraint(equalTo: view.topAnchor, constant: 8), + searchView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), + searchView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), + searchView.heightAnchor.constraint(equalToConstant: 32), + + segmentView.topAnchor.constraint(equalTo: searchView.bottomAnchor), + segmentView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), + segmentView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), + segmentView.heightAnchor.constraint(equalToConstant: 32), + + tableView.topAnchor.constraint(equalTo: segmentView.bottomAnchor, constant: 8), + tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + } + + open override func bindViews() { + super.bindViews() + + viewModel.logsListView = self + tableView.register(LogEntryTableViewCell.self, forCellReuseIdentifier: "identifier") + } + + open override func configureAppearance() { + super.configureAppearance() + + configureSegmentView() + view.backgroundColor = .systemBackground + tableView.backgroundColor = .systemBackground + } + + // MARK: - LogsListViewOutput + + open func reloadTableView() { + applySnapshot() + } + + // MARK: - Open methods + + open func createDataSource() -> DataSource { + let cellProvider: DataSource.CellProvider = { collectionView, indexPath, itemIdentifier in + let cell = collectionView.dequeueReusableCell(withIdentifier: "identifier", + for: indexPath) as? LogEntryTableViewCell + + cell?.configure(with: itemIdentifier) + + return cell + } + + return .init(tableView: tableView, cellProvider: cellProvider) + } + + open func applySnapshot() { + var snapshot = Snapshot() + + snapshot.appendSections([TableSection.main.rawValue]) + snapshot.appendItems(viewModel.filteredLogs, toSection: TableSection.main.rawValue) + + dataSource.apply(snapshot, animatingDifferences: true) + } + + // MARK: - Private methods + + private func configureSegmentView() { + for (index, segment) in LogsStorageViewModel.LevelType.allCases.enumerated() { + let action = UIAction(title: segment.rawValue, handler: viewModel.actionHandler(for:)) + segmentView.insertSegment(action: action, at: index, animated: false) + } + + segmentView.selectedSegmentIndex = 0 + } +} diff --git a/TILogging/Sources/LogsStorageViewModel.swift b/TILogging/Sources/LogsStorageViewModel.swift new file mode 100644 index 00000000..9a586af0 --- /dev/null +++ b/TILogging/Sources/LogsStorageViewModel.swift @@ -0,0 +1,80 @@ +import TISwiftUtils +import UIKit +import OSLog + +public protocol LogsListViewOutput: AnyObject { + func reloadTableView() +} + +@available(iOS 15, *) +open class LogsStorageViewModel { + + public enum LevelType: String, CaseIterable { + case all + case `default` + case info + case debug + case error + case fault + } + + private var allLogs: [OSLogEntryLog] = [] { + didSet { + filterLogs() + } + } + + public var filteredLogs: [OSLogEntryLog] = [] { + didSet { + logsListView?.reloadTableView() + } + } + + weak public var logsListView: LogsListViewOutput? + + public init() { + loadLogs() + } + + open func loadLogs() { + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + let logStore = try? OSLogStore(scope: .currentProcessIdentifier) + let entries = try? logStore?.getEntries() + + let logs = entries? + .compactMap { $0 as? OSLogEntryLog } + + DispatchQueue.main.async { + self?.allLogs = logs ?? [] + } + } + } + + open func filterLogs(filter: Closure? = nil) { + filteredLogs = allLogs.filter { filter?($0) ?? true } + } + + open func actionHandler(for action: UIAction) { + guard let level = LevelType(rawValue: action.title) else { return } + + switch level { + case .all: + loadLogs() + + case .info: + filterLogs(filter: { $0.level == .info }) + + case .default: + filterLogs(filter: { $0.level == .notice }) + + case .debug: + filterLogs(filter: { $0.level == .debug }) + + case .error: + filterLogs(filter: { $0.level == .error }) + + case .fault: + filterLogs(filter: { $0.level == .fault }) + } + } +} diff --git a/TILogging/TILogging.podspec b/TILogging/TILogging.podspec index cbcd20ef..f5804527 100644 --- a/TILogging/TILogging.podspec +++ b/TILogging/TILogging.podspec @@ -12,4 +12,8 @@ Pod::Spec.new do |s| s.source_files = s.name + '/Sources/**/*' + s.dependency 'TIUIKitCore', s.version.to_s + s.dependency 'TISwiftUtils', s.version.to_s + s.dependency 'TIUIElements', s.version.to_s + end