mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-25 09:41:00 +00:00
Load more WIP
This commit is contained in:
parent
9507343511
commit
7f937601b1
18 changed files with 255 additions and 23 deletions
|
@ -74,6 +74,7 @@ extension ContentDatabase {
|
|||
try db.create(table: "loadMoreRecord") { t in
|
||||
t.column("timelineId").notNull().references("timelineRecord", onDelete: .cascade)
|
||||
t.column("afterStatusId", .text).notNull()
|
||||
t.column("beforeStatusId", .text).notNull()
|
||||
|
||||
t.primaryKey(["timelineId", "afterStatusId"], onConflict: .replace)
|
||||
}
|
||||
|
|
|
@ -49,7 +49,10 @@ public extension ContentDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func insert(statuses: [Status], timeline: Timeline) -> AnyPublisher<Never, Error> {
|
||||
func insert(
|
||||
statuses: [Status],
|
||||
timeline: Timeline,
|
||||
loadMoreAndDirection: (LoadMore, LoadMore.Direction)? = nil) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher {
|
||||
let timelineRecord = TimelineRecord(timeline: timeline)
|
||||
|
||||
|
@ -66,7 +69,38 @@ public extension ContentDatabase {
|
|||
if let maxIDPresent = maxIDPresent,
|
||||
let minIDInserted = statuses.map(\.id).min(),
|
||||
minIDInserted > maxIDPresent {
|
||||
try LoadMoreRecord(timelineId: timeline.id, afterStatusId: minIDInserted).save($0)
|
||||
try LoadMoreRecord(
|
||||
timelineId: timeline.id,
|
||||
afterStatusId: minIDInserted,
|
||||
beforeStatusId: maxIDPresent)
|
||||
.save($0)
|
||||
}
|
||||
|
||||
guard let (loadMore, direction) = loadMoreAndDirection else { return }
|
||||
|
||||
try LoadMoreRecord(
|
||||
timelineId: loadMore.timeline.id,
|
||||
afterStatusId: loadMore.afterStatusId,
|
||||
beforeStatusId: loadMore.beforeStatusId)
|
||||
.delete($0)
|
||||
|
||||
switch direction {
|
||||
case .up:
|
||||
if let maxIDInserted = statuses.map(\.id).max(), maxIDInserted < loadMore.afterStatusId {
|
||||
try LoadMoreRecord(
|
||||
timelineId: loadMore.timeline.id,
|
||||
afterStatusId: loadMore.afterStatusId,
|
||||
beforeStatusId: maxIDInserted)
|
||||
.save($0)
|
||||
}
|
||||
case .down:
|
||||
if let minIDInserted = statuses.map(\.id).min(), minIDInserted > loadMore.beforeStatusId {
|
||||
try LoadMoreRecord(
|
||||
timelineId: loadMore.timeline.id,
|
||||
afterStatusId: minIDInserted,
|
||||
beforeStatusId: loadMore.beforeStatusId)
|
||||
.save($0)
|
||||
}
|
||||
}
|
||||
}
|
||||
.ignoreOutput()
|
||||
|
|
|
@ -7,12 +7,14 @@ import Mastodon
|
|||
struct LoadMoreRecord: Codable, Hashable {
|
||||
let timelineId: String
|
||||
let afterStatusId: String
|
||||
let beforeStatusId: String
|
||||
}
|
||||
|
||||
extension LoadMoreRecord {
|
||||
enum Columns {
|
||||
static let timelineId = Column(LoadMoreRecord.CodingKeys.timelineId)
|
||||
static let afterStatusId = Column(LoadMoreRecord.CodingKeys.afterStatusId)
|
||||
static let beforeStatusId = Column(LoadMoreRecord.CodingKeys.beforeStatusId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,10 @@ extension TimelineItemsInfo {
|
|||
}) else { continue }
|
||||
|
||||
timelineItems.insert(
|
||||
.loadMore(LoadMore(timeline: timeline, afterStatusId: loadMoreRecord.afterStatusId)),
|
||||
.loadMore(LoadMore(
|
||||
timeline: timeline,
|
||||
afterStatusId: loadMoreRecord.afterStatusId,
|
||||
beforeStatusId: loadMoreRecord.beforeStatusId)),
|
||||
at: index)
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ extension TimelineRecord {
|
|||
StatusRecord.self,
|
||||
through: statusJoins,
|
||||
using: TimelineStatusJoin.status)
|
||||
.order(StatusRecord.Columns.createdAt.desc)
|
||||
.order(StatusRecord.Columns.id.desc)
|
||||
static let account = belongsTo(AccountRecord.self, using: ForeignKey([Columns.accountId]))
|
||||
static let loadMores = hasMany(LoadMoreRecord.self)
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import Mastodon
|
||||
|
||||
public struct LoadMore: Hashable {
|
||||
public let timeline: Timeline
|
||||
public let afterStatusId: String
|
||||
public let beforeStatusId: String
|
||||
}
|
||||
|
||||
public extension LoadMore {
|
||||
|
|
|
@ -54,6 +54,8 @@
|
|||
D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0E2C1D024FD97F000854680 /* ViewModels */; };
|
||||
D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */; };
|
||||
D0E5362024E3EB4D00FB1CE1 /* Notification Service Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
D0E569DB2529319100FA1D72 /* LoadMoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E569DA2529319100FA1D72 /* LoadMoreView.swift */; };
|
||||
D0E569E0252931B100FA1D72 /* LoadMoreContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E569DF252931B100FA1D72 /* LoadMoreContentConfiguration.swift */; };
|
||||
D0EA59402522AC8700804347 /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EA593F2522AC8700804347 /* CardView.swift */; };
|
||||
D0EA59482522B8B600804347 /* ViewConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EA59472522B8B600804347 /* ViewConstants.swift */; };
|
||||
D0F0B10E251A868200942152 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0B10D251A868200942152 /* AccountView.swift */; };
|
||||
|
@ -154,6 +156,8 @@
|
|||
D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||
D0E5361D24E3EB4D00FB1CE1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D0E5362824E4A06B00FB1CE1 /* Notification Service Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Notification Service Extension.entitlements"; sourceTree = "<group>"; };
|
||||
D0E569DA2529319100FA1D72 /* LoadMoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreView.swift; sourceTree = "<group>"; };
|
||||
D0E569DF252931B100FA1D72 /* LoadMoreContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreContentConfiguration.swift; sourceTree = "<group>"; };
|
||||
D0EA593F2522AC8700804347 /* CardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = "<group>"; };
|
||||
D0EA59472522B8B600804347 /* ViewConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewConstants.swift; sourceTree = "<group>"; };
|
||||
D0F0B10D251A868200942152 /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = "<group>"; };
|
||||
|
@ -291,6 +295,8 @@
|
|||
D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */,
|
||||
D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */,
|
||||
D0B8510B25259E56004E0744 /* LoadMoreCell.swift */,
|
||||
D0E569DF252931B100FA1D72 /* LoadMoreContentConfiguration.swift */,
|
||||
D0E569DA2529319100FA1D72 /* LoadMoreView.swift */,
|
||||
D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */,
|
||||
D0C7D42824F76169001EBDBB /* PostingReadingPreferencesView.swift */,
|
||||
D0C7D42624F76169001EBDBB /* PreferencesView.swift */,
|
||||
|
@ -534,6 +540,7 @@
|
|||
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
|
||||
D0F0B126251A90F400942152 /* AccountListCell.swift in Sources */,
|
||||
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */,
|
||||
D0E569E0252931B100FA1D72 /* LoadMoreContentConfiguration.swift in Sources */,
|
||||
D0F0B136251AA12700942152 /* CollectionItemKind+Extensions.swift in Sources */,
|
||||
D0625E5D250F0B5C00502611 /* StatusContentConfiguration.swift in Sources */,
|
||||
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */,
|
||||
|
@ -544,6 +551,7 @@
|
|||
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */,
|
||||
D0625E5F250F0CFF00502611 /* StatusView.swift in Sources */,
|
||||
D0625E59250F092900502611 /* StatusListCell.swift in Sources */,
|
||||
D0E569DB2529319100FA1D72 /* LoadMoreView.swift in Sources */,
|
||||
D0C7D49D24F7616A001EBDBB /* PostingReadingPreferencesView.swift in Sources */,
|
||||
D0B5FE9B251583DB00478838 /* ProfileCollection+Extensions.swift in Sources */,
|
||||
D0C7D49E24F7616A001EBDBB /* SecondaryNavigationView.swift in Sources */,
|
||||
|
|
|
@ -21,8 +21,13 @@ public extension LoadMoreService {
|
|||
mastodonAPIClient.pagedRequest(
|
||||
loadMore.timeline.endpoint,
|
||||
maxID: direction == .down ? loadMore.afterStatusId : nil,
|
||||
minID: direction == .up ? loadMore.afterStatusId : nil)
|
||||
.flatMap { contentDatabase.insert(statuses: $0.result, timeline: loadMore.timeline) }
|
||||
minID: direction == .up ? loadMore.beforeStatusId : nil)
|
||||
.flatMap {
|
||||
contentDatabase.insert(
|
||||
statuses: $0.result,
|
||||
timeline: loadMore.timeline,
|
||||
loadMoreAndDirection: (loadMore, direction))
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,6 +74,10 @@ public extension NavigationService {
|
|||
func accountService(account: Account) -> AccountService {
|
||||
AccountService(account: account, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||
}
|
||||
|
||||
func loadMoreService(loadMore: LoadMore) -> LoadMoreService {
|
||||
LoadMoreService(loadMore: loadMore, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||
}
|
||||
}
|
||||
|
||||
private extension NavigationService {
|
||||
|
|
|
@ -28,11 +28,7 @@ class TableViewController: UITableViewController {
|
|||
case (let accountListCell as AccountListCell, let accountViewModel as AccountViewModel):
|
||||
accountListCell.viewModel = accountViewModel
|
||||
case (let loadMoreCell as LoadMoreCell, let loadMoreViewModel as LoadMoreViewModel):
|
||||
var contentConfiguration = loadMoreCell.defaultContentConfiguration()
|
||||
|
||||
contentConfiguration.text = NSLocalizedString("load-more", comment: "")
|
||||
|
||||
loadMoreCell.contentConfiguration = contentConfiguration
|
||||
loadMoreCell.viewModel = loadMoreViewModel
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,7 +1,31 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import ServiceLayer
|
||||
|
||||
public class LoadMoreViewModel: ObservableObject {
|
||||
@Published var loading = false
|
||||
public struct LoadMoreViewModel {
|
||||
public let loading: AnyPublisher<Bool, Never>
|
||||
public let events: AnyPublisher<AnyPublisher<CollectionItemEvent, Error>, Never>
|
||||
|
||||
private let loadMoreService: LoadMoreService
|
||||
private let eventsSubject = PassthroughSubject<AnyPublisher<CollectionItemEvent, Error>, Never>()
|
||||
private let loadingSubject = PassthroughSubject<Bool, Never>()
|
||||
|
||||
init(loadMoreService: LoadMoreService) {
|
||||
self.loadMoreService = loadMoreService
|
||||
loading = loadingSubject.eraseToAnyPublisher()
|
||||
events = eventsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
extension LoadMoreViewModel {
|
||||
func loadMore() {
|
||||
eventsSubject.send(
|
||||
loadMoreService.request(direction: .down)
|
||||
.handleEvents(
|
||||
receiveSubscription: { _ in loadingSubject.send(true) },
|
||||
receiveCompletion: { _ in loadingSubject.send(false) })
|
||||
.map { _ in CollectionItemEvent.ignorableOutput }
|
||||
.eraseToAnyPublisher())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,8 +67,8 @@ extension StatusListViewModel: CollectionViewModel {
|
|||
statusListService: statusListService
|
||||
.navigationService
|
||||
.contextStatusListService(id: configuration.status.displayStatus.id))))
|
||||
default:
|
||||
break
|
||||
case .loadMore:
|
||||
loadMoreViewModel(item: item)?.loadMore()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ extension StatusListViewModel: CollectionViewModel {
|
|||
case .status:
|
||||
return statusViewModel(item: item)
|
||||
case .loadMore:
|
||||
return LoadMoreViewModel()
|
||||
return loadMoreViewModel(item: item)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
@ -127,6 +127,33 @@ private extension StatusListViewModel {
|
|||
return statusViewModel
|
||||
}
|
||||
|
||||
func loadMoreViewModel(item: CollectionItemIdentifier) -> LoadMoreViewModel? {
|
||||
guard let timelineItem = timelineItems[item],
|
||||
case let .loadMore(loadMore) = timelineItem
|
||||
else { return nil }
|
||||
|
||||
if let cachedViewModel = viewModelCache[timelineItem]?.0 as? LoadMoreViewModel {
|
||||
return cachedViewModel
|
||||
}
|
||||
|
||||
let loadMoreViewModel = LoadMoreViewModel(
|
||||
loadMoreService: statusListService.navigationService.loadMoreService(loadMore: loadMore))
|
||||
|
||||
viewModelCache[timelineItem] = (loadMoreViewModel, loadMoreViewModel.events
|
||||
.flatMap { $0 }
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
.sink { [weak self] in
|
||||
guard
|
||||
let self = self,
|
||||
let event = NavigationEvent($0)
|
||||
else { return }
|
||||
|
||||
self.navigationEventsSubject.send(event)
|
||||
})
|
||||
|
||||
return loadMoreViewModel
|
||||
}
|
||||
|
||||
func process(sections: [[Timeline.Item]]) {
|
||||
determineIfScrollPositionShouldBeMaintained(newSections: sections)
|
||||
|
||||
|
|
|
@ -15,9 +15,12 @@ class AccountListCell: UITableViewCell {
|
|||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let isPhoneIdiom = UIDevice.current.userInterfaceIdiom == .phone
|
||||
|
||||
separatorInset.right = isPhoneIdiom ? 0 : layoutMargins.right
|
||||
separatorInset.left = isPhoneIdiom ? 0 : layoutMargins.left
|
||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||
separatorInset.left = 0
|
||||
separatorInset.right = 0
|
||||
} else {
|
||||
separatorInset.left = layoutMargins.left
|
||||
separatorInset.right = layoutMargins.right
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,26 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
class LoadMoreCell: UITableViewCell {
|
||||
var viewModel: LoadMoreViewModel?
|
||||
|
||||
override func updateConfiguration(using state: UICellConfigurationState) {
|
||||
guard let viewModel = viewModel else { return }
|
||||
|
||||
contentConfiguration = LoadMoreContentConfiguration(viewModel: viewModel)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
separatorInset.left = UIDevice.current.userInterfaceIdiom == .phone ? 0 : layoutMargins.left
|
||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||
separatorInset.left = 0
|
||||
separatorInset.right = 0
|
||||
} else {
|
||||
separatorInset.left = layoutMargins.left
|
||||
separatorInset.right = layoutMargins.right
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
18
Views/LoadMoreContentConfiguration.swift
Normal file
18
Views/LoadMoreContentConfiguration.swift
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
struct LoadMoreContentConfiguration {
|
||||
let viewModel: LoadMoreViewModel
|
||||
}
|
||||
|
||||
extension LoadMoreContentConfiguration: UIContentConfiguration {
|
||||
func makeContentView() -> UIView & UIContentView {
|
||||
LoadMoreView(configuration: self)
|
||||
}
|
||||
|
||||
func updated(for state: UIConfigurationState) -> LoadMoreContentConfiguration {
|
||||
self
|
||||
}
|
||||
}
|
91
Views/LoadMoreView.swift
Normal file
91
Views/LoadMoreView.swift
Normal file
|
@ -0,0 +1,91 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import UIKit
|
||||
|
||||
class LoadMoreView: UIView {
|
||||
private let label = UILabel()
|
||||
private let activityIndicatorView = UIActivityIndicatorView()
|
||||
private var loadMoreConfiguration: LoadMoreContentConfiguration
|
||||
private var loadingCancellable: AnyCancellable?
|
||||
|
||||
init(configuration: LoadMoreContentConfiguration) {
|
||||
self.loadMoreConfiguration = configuration
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
initialSetup()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
extension LoadMoreView: UIContentView {
|
||||
var configuration: UIContentConfiguration {
|
||||
get { loadMoreConfiguration }
|
||||
set {
|
||||
guard let loadMoreConfiguration = newValue as? LoadMoreContentConfiguration else { return }
|
||||
|
||||
self.loadMoreConfiguration = loadMoreConfiguration
|
||||
|
||||
applyLoadMoreConfiguration()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension LoadMoreView {
|
||||
func initialSetup() {
|
||||
let leadingArrowImageView = UIImageView()
|
||||
let trailingArrowImageView = UIImageView()
|
||||
|
||||
for arrowImageView in [leadingArrowImageView, trailingArrowImageView] {
|
||||
addSubview(arrowImageView)
|
||||
arrowImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
arrowImageView.image = UIImage(
|
||||
systemName: "arrow.up.circle",
|
||||
withConfiguration: UIImage.SymbolConfiguration(
|
||||
pointSize: UIFont.preferredFont(forTextStyle: .title2).pointSize))
|
||||
arrowImageView.setContentHuggingPriority(.required, for: .horizontal)
|
||||
}
|
||||
|
||||
addSubview(label)
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textAlignment = .center
|
||||
label.font = .preferredFont(forTextStyle: .title2)
|
||||
label.adjustsFontForContentSizeCategory = true
|
||||
label.textColor = label.tintColor
|
||||
label.text = NSLocalizedString("load-more", comment: "")
|
||||
label.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||
|
||||
addSubview(activityIndicatorView)
|
||||
activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
|
||||
activityIndicatorView.hidesWhenStopped = true
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
leadingArrowImageView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
|
||||
leadingArrowImageView.topAnchor.constraint(equalTo: readableContentGuide.topAnchor),
|
||||
leadingArrowImageView.bottomAnchor.constraint(equalTo: readableContentGuide.bottomAnchor),
|
||||
label.leadingAnchor.constraint(equalTo: leadingArrowImageView.trailingAnchor),
|
||||
label.topAnchor.constraint(greaterThanOrEqualTo: readableContentGuide.topAnchor),
|
||||
label.bottomAnchor.constraint(greaterThanOrEqualTo: readableContentGuide.bottomAnchor),
|
||||
label.trailingAnchor.constraint(equalTo: trailingArrowImageView.leadingAnchor),
|
||||
trailingArrowImageView.topAnchor.constraint(equalTo: readableContentGuide.topAnchor),
|
||||
trailingArrowImageView.bottomAnchor.constraint(equalTo: readableContentGuide.bottomAnchor),
|
||||
trailingArrowImageView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
|
||||
activityIndicatorView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
activityIndicatorView.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
func applyLoadMoreConfiguration() {
|
||||
loadingCancellable = loadMoreConfiguration.viewModel.loading.sink { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.label.isHidden = $0
|
||||
$0 ? self.activityIndicatorView.startAnimating() : self.activityIndicatorView.stopAnimating()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -125,7 +125,7 @@ private extension StatusView {
|
|||
])
|
||||
|
||||
for constraint in separatorConstraints {
|
||||
constraint.constant = 1 / UIScreen.main.scale
|
||||
constraint.constant = .hairline
|
||||
}
|
||||
|
||||
avatarImageView.kf.indicatorType = .activity
|
||||
|
|
|
@ -6,6 +6,7 @@ extension CGFloat {
|
|||
static let defaultSpacing: Self = 8
|
||||
static let compactSpacing: Self = 4
|
||||
static let defaultCornerRadius: Self = 8
|
||||
static let hairline = 1 / UIScreen.main.scale
|
||||
}
|
||||
|
||||
extension TimeInterval {
|
||||
|
|
Loading…
Reference in a new issue