mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-22 00:01:00 +00:00
Explore instance section and profile directory
This commit is contained in:
parent
5f03ce7d8b
commit
e62e87510d
14 changed files with 296 additions and 45 deletions
|
@ -8,13 +8,35 @@ import ViewModels
|
|||
final class ExploreDataSource: UICollectionViewDiffableDataSource<ExploreViewModel.Section, ExploreViewModel.Item> {
|
||||
private let updateQueue =
|
||||
DispatchQueue(label: "com.metabolist.metatext.explore-data-source.update-queue")
|
||||
private weak var collectionView: UICollectionView?
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(collectionView: UICollectionView, viewModel: ExploreViewModel) {
|
||||
self.collectionView = collectionView
|
||||
let tagRegistration = UICollectionView.CellRegistration<TagCollectionViewCell, TagViewModel> {
|
||||
$0.viewModel = $2
|
||||
}
|
||||
|
||||
let instanceRegistration = UICollectionView.CellRegistration<InstanceCollectionViewCell, InstanceViewModel> {
|
||||
$0.viewModel = $2
|
||||
}
|
||||
|
||||
let itemRegistration = UICollectionView.CellRegistration
|
||||
<SeparatorConfiguredCollectionViewListCell, ExploreViewModel.Item> {
|
||||
var configuration = $0.defaultContentConfiguration()
|
||||
|
||||
switch $2 {
|
||||
case .profileDirectory:
|
||||
configuration.text = NSLocalizedString("explore.profile-directory", comment: "")
|
||||
configuration.image = UIImage(systemName: "person.crop.square.fill.and.at.rectangle")
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
$0.contentConfiguration = configuration
|
||||
$0.accessories = [.disclosureIndicator()]
|
||||
}
|
||||
|
||||
super.init(collectionView: collectionView) {
|
||||
switch $2 {
|
||||
case let .tag(tag):
|
||||
|
@ -22,6 +44,13 @@ final class ExploreDataSource: UICollectionViewDiffableDataSource<ExploreViewMod
|
|||
using: tagRegistration,
|
||||
for: $1,
|
||||
item: viewModel.viewModel(tag: tag))
|
||||
case .instance:
|
||||
return $0.dequeueConfiguredReusableCell(
|
||||
using: instanceRegistration,
|
||||
for: $1,
|
||||
item: viewModel.instanceViewModel)
|
||||
default:
|
||||
return $0.dequeueConfiguredReusableCell(using: itemRegistration, for: $1, item: $2)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,19 +63,9 @@ final class ExploreDataSource: UICollectionViewDiffableDataSource<ExploreViewMod
|
|||
$0.dequeueConfiguredReusableSupplementary(using: headerRegistration, for: $2)
|
||||
}
|
||||
|
||||
viewModel.$trends.sink { [weak self] tags in
|
||||
guard let self = self else { return }
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<ExploreViewModel.Section, ExploreViewModel.Item>()
|
||||
|
||||
if !tags.isEmpty {
|
||||
snapshot.appendSections([.trending])
|
||||
snapshot.appendItems(tags.map(ExploreViewModel.Item.tag), toSection: .trending)
|
||||
}
|
||||
|
||||
self.apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
viewModel.$trends.combineLatest(viewModel.$instanceViewModel)
|
||||
.sink { [weak self] in self?.update(tags: $0, instanceViewModel: $1) }
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
override func apply(_ snapshot: NSDiffableDataSourceSnapshot<ExploreViewModel.Section, ExploreViewModel.Item>,
|
||||
|
@ -58,6 +77,35 @@ final class ExploreDataSource: UICollectionViewDiffableDataSource<ExploreViewMod
|
|||
}
|
||||
}
|
||||
|
||||
private extension ExploreDataSource {
|
||||
func update(tags: [Tag], instanceViewModel: InstanceViewModel?) {
|
||||
var snapshot = NSDiffableDataSourceSnapshot<ExploreViewModel.Section, ExploreViewModel.Item>()
|
||||
|
||||
if !tags.isEmpty {
|
||||
snapshot.appendSections([.trending])
|
||||
snapshot.appendItems(tags.map(ExploreViewModel.Item.tag), toSection: .trending)
|
||||
}
|
||||
|
||||
if let instanceViewModel = instanceViewModel {
|
||||
snapshot.appendSections([.instance])
|
||||
snapshot.appendItems([.instance], toSection: .instance)
|
||||
|
||||
if instanceViewModel.instance.canShowProfileDirectory {
|
||||
snapshot.appendItems([.profileDirectory], toSection: .instance)
|
||||
}
|
||||
}
|
||||
|
||||
let wasEmpty = self.snapshot().itemIdentifiers.isEmpty
|
||||
let contentOffset = collectionView?.contentOffset
|
||||
|
||||
apply(snapshot, animatingDifferences: false) {
|
||||
if let contentOffset = contentOffset, !wasEmpty {
|
||||
self.collectionView?.contentOffset = contentOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ExploreViewModel.Section {
|
||||
var displayName: String {
|
||||
switch self {
|
||||
|
|
|
@ -79,6 +79,7 @@
|
|||
"emoji.system-group.flags" = "Flags";
|
||||
"explore.trending" = "Trending Now";
|
||||
"explore.instance" = "Instance";
|
||||
"explore.profile-directory" = "Profile Directory";
|
||||
"error" = "Error";
|
||||
"favorites" = "Favorites";
|
||||
"follow-requests" = "Follow Requests";
|
||||
|
|
|
@ -53,3 +53,33 @@ public struct Instance: Codable, Hashable {
|
|||
self.maxTootChars = maxTootChars
|
||||
}
|
||||
}
|
||||
|
||||
public extension Instance {
|
||||
var majorVersion: Int? {
|
||||
guard let majorVersionString = version.split(separator: ".").first else { return nil }
|
||||
|
||||
return Int(majorVersionString)
|
||||
}
|
||||
|
||||
var minorVersion: Int? {
|
||||
let versionComponents = version.split(separator: ".")
|
||||
|
||||
guard versionComponents.count > 1 else { return nil }
|
||||
|
||||
return Int(versionComponents[1])
|
||||
}
|
||||
|
||||
var patchVersion: String? {
|
||||
let versionComponents = version.split(separator: ".")
|
||||
|
||||
guard versionComponents.count > 2 else { return nil }
|
||||
|
||||
return String(versionComponents[2])
|
||||
}
|
||||
|
||||
var canShowProfileDirectory: Bool {
|
||||
guard let majorVersion = majorVersion else { return false }
|
||||
|
||||
return majorVersion >= 3
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ public enum AccountsEndpoint {
|
|||
case accountsFollowers(id: Account.Id)
|
||||
case accountsFollowing(id: Account.Id)
|
||||
case followRequests
|
||||
case directory(local: Bool)
|
||||
}
|
||||
|
||||
extension AccountsEndpoint: Endpoint {
|
||||
|
@ -21,7 +22,7 @@ extension AccountsEndpoint: Endpoint {
|
|||
switch self {
|
||||
case .rebloggedBy, .favouritedBy:
|
||||
return defaultContext + ["statuses"]
|
||||
case .mutes, .blocks, .followRequests:
|
||||
case .mutes, .blocks, .followRequests, .directory:
|
||||
return defaultContext
|
||||
case .accountsFollowers, .accountsFollowing:
|
||||
return defaultContext + ["accounts"]
|
||||
|
@ -44,6 +45,17 @@ extension AccountsEndpoint: Endpoint {
|
|||
return [id, "following"]
|
||||
case .followRequests:
|
||||
return ["follow_requests"]
|
||||
case .directory:
|
||||
return ["directory"]
|
||||
}
|
||||
}
|
||||
|
||||
public var queryParameters: [URLQueryItem] {
|
||||
switch self {
|
||||
case let .directory(local):
|
||||
return [.init(name: "local", value: String(local))]
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -111,6 +111,10 @@
|
|||
D08E52F8257D78BE00FA2C5F /* ViewConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EA59472522B8B600804347 /* ViewConstants.swift */; };
|
||||
D097F41B25BE3E1A00859F2C /* SearchScope+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D097F41A25BE3E1A00859F2C /* SearchScope+Extensions.swift */; };
|
||||
D097F4C125BFA04C00859F2C /* NotificationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D097F4C025BFA04C00859F2C /* NotificationsViewController.swift */; };
|
||||
D09D970825C64522007E6394 /* InstanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09D970725C64522007E6394 /* InstanceView.swift */; };
|
||||
D09D970E25C64539007E6394 /* InstanceContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09D970D25C64539007E6394 /* InstanceContentConfiguration.swift */; };
|
||||
D09D971825C64682007E6394 /* InstanceCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09D971725C64682007E6394 /* InstanceCollectionViewCell.swift */; };
|
||||
D09D972225C65682007E6394 /* SeparatorConfiguredCollectionViewListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09D972125C65682007E6394 /* SeparatorConfiguredCollectionViewListCell.swift */; };
|
||||
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */; };
|
||||
D0A7AC7325748BFF00E4E8AB /* ReportStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */; };
|
||||
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; };
|
||||
|
@ -293,6 +297,10 @@
|
|||
D08E52ED257D757100FA2C5F /* CompositionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionView.swift; sourceTree = "<group>"; };
|
||||
D097F41A25BE3E1A00859F2C /* SearchScope+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchScope+Extensions.swift"; sourceTree = "<group>"; };
|
||||
D097F4C025BFA04C00859F2C /* NotificationsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsViewController.swift; sourceTree = "<group>"; };
|
||||
D09D970725C64522007E6394 /* InstanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceView.swift; sourceTree = "<group>"; };
|
||||
D09D970D25C64539007E6394 /* InstanceContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceContentConfiguration.swift; sourceTree = "<group>"; };
|
||||
D09D971725C64682007E6394 /* InstanceCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D09D972125C65682007E6394 /* SeparatorConfiguredCollectionViewListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorConfiguredCollectionViewListCell.swift; sourceTree = "<group>"; };
|
||||
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = "<group>"; };
|
||||
D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusView.swift; sourceTree = "<group>"; };
|
||||
D0AD03552505814D0085A466 /* Base16 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base16; sourceTree = "<group>"; };
|
||||
|
@ -492,6 +500,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D07EC7DB25B13DBB006DF726 /* EmojiCollectionViewCell.swift */,
|
||||
D09D971725C64682007E6394 /* InstanceCollectionViewCell.swift */,
|
||||
D09D972125C65682007E6394 /* SeparatorConfiguredCollectionViewListCell.swift */,
|
||||
D0DDA77425C5F73F00FA0F91 /* TagCollectionViewCell.swift */,
|
||||
);
|
||||
path = "Collection View Cells";
|
||||
|
@ -505,6 +515,7 @@
|
|||
D07EC7E225B13DD3006DF726 /* EmojiContentConfiguration.swift */,
|
||||
D07EC7F125B13E57006DF726 /* EmojiView.swift */,
|
||||
D021A61925C36C1A008A0C0D /* IdentityContentConfiguration.swift */,
|
||||
D09D970D25C64539007E6394 /* InstanceContentConfiguration.swift */,
|
||||
D0E569DF252931B100FA1D72 /* LoadMoreContentConfiguration.swift */,
|
||||
D036AA0B254B612B009094DF /* NotificationContentConfiguration.swift */,
|
||||
D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */,
|
||||
|
@ -519,6 +530,7 @@
|
|||
D0F0B10D251A868200942152 /* AccountView.swift */,
|
||||
D00702302555F4AE00F38136 /* ConversationView.swift */,
|
||||
D021A61325C36BFB008A0C0D /* IdentityView.swift */,
|
||||
D09D970725C64522007E6394 /* InstanceView.swift */,
|
||||
D0E569DA2529319100FA1D72 /* LoadMoreView.swift */,
|
||||
D036AA06254B6118009094DF /* NotificationView.swift */,
|
||||
D00CB2EC2533ACC00080096B /* StatusView.swift */,
|
||||
|
@ -961,6 +973,7 @@
|
|||
D0CE9F87258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */,
|
||||
D0F0B113251A86A000942152 /* AccountContentConfiguration.swift in Sources */,
|
||||
D05E688525B55AE8001FB2C6 /* AVURLAsset+Extensions.swift in Sources */,
|
||||
D09D970E25C64539007E6394 /* InstanceContentConfiguration.swift in Sources */,
|
||||
D036AA02254B6101009094DF /* NotificationTableViewCell.swift in Sources */,
|
||||
D08B8D42253F92B600B1EBEF /* ImagePageViewController.swift in Sources */,
|
||||
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
|
||||
|
@ -989,6 +1002,7 @@
|
|||
D07EC7F225B13E57006DF726 /* EmojiView.swift in Sources */,
|
||||
D05936CF25A8D79800754FDF /* EditAttachmentViewController.swift in Sources */,
|
||||
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */,
|
||||
D09D970825C64522007E6394 /* InstanceView.swift in Sources */,
|
||||
D021A62C25C38570008A0C0D /* AboutView.swift in Sources */,
|
||||
D00702362555F4C500F38136 /* ConversationContentConfiguration.swift in Sources */,
|
||||
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */,
|
||||
|
@ -1043,9 +1057,11 @@
|
|||
D0DDA76B25C5F20800FA0F91 /* ExploreDataSource.swift in Sources */,
|
||||
D035F88725B8016000DC75ED /* NavigationViewModel+Extensions.swift in Sources */,
|
||||
D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */,
|
||||
D09D972225C65682007E6394 /* SeparatorConfiguredCollectionViewListCell.swift in Sources */,
|
||||
D088406D25AFBBE200BB749B /* EmojiPickerViewController.swift in Sources */,
|
||||
D07EC7FD25B16994006DF726 /* EmojiCategoryHeaderView.swift in Sources */,
|
||||
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */,
|
||||
D09D971825C64682007E6394 /* InstanceCollectionViewCell.swift in Sources */,
|
||||
D0D2AC3925BBEC0F003D5DF2 /* CollectionSection+Extensions.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
@ -35,6 +35,7 @@ final class ExploreViewController: UICollectionViewController {
|
|||
|
||||
collectionView.dataSource = dataSource
|
||||
collectionView.backgroundColor = .systemBackground
|
||||
collectionView.contentInset.bottom = Self.bottomInset
|
||||
clearsSelectionOnViewWillAppear = true
|
||||
|
||||
collectionView.refreshControl = UIRefreshControl()
|
||||
|
@ -92,6 +93,11 @@ final class ExploreViewController: UICollectionViewController {
|
|||
viewModel.refresh()
|
||||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView,
|
||||
shouldHighlightItemAt indexPath: IndexPath) -> Bool {
|
||||
dataSource.itemIdentifier(for: indexPath) != .instance
|
||||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
|
||||
|
||||
|
@ -115,12 +121,23 @@ extension ExploreViewController: UISearchResultsUpdating {
|
|||
}
|
||||
|
||||
private extension ExploreViewController {
|
||||
static let bottomInset: CGFloat = .newStatusButtonDimension + .defaultSpacing * 4
|
||||
|
||||
static func layout() -> UICollectionViewLayout {
|
||||
var config = UICollectionLayoutListConfiguration(appearance: .plain)
|
||||
var listConfiguration = UICollectionLayoutListConfiguration(appearance: .plain)
|
||||
|
||||
config.headerMode = .supplementary
|
||||
listConfiguration.headerMode = .supplementary
|
||||
|
||||
return UICollectionViewCompositionalLayout.list(using: config)
|
||||
return UICollectionViewCompositionalLayout(
|
||||
sectionProvider: {
|
||||
let section = NSCollectionLayoutSection.list(using: listConfiguration, layoutEnvironment: $1)
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
section.contentInsetsReference = .readableContent
|
||||
}
|
||||
|
||||
return section
|
||||
})
|
||||
}
|
||||
|
||||
func handle(event: ExploreViewModel.Event) {
|
||||
|
|
|
@ -49,6 +49,8 @@ public extension ExploreViewModel {
|
|||
|
||||
enum Item: Hashable {
|
||||
case tag(Tag)
|
||||
case instance
|
||||
case profileDirectory
|
||||
}
|
||||
|
||||
func refresh() {
|
||||
|
@ -78,6 +80,14 @@ public extension ExploreViewModel {
|
|||
.navigation(.collection(exploreService
|
||||
.navigationService
|
||||
.timelineService(timeline: .tag(tag.name)))))
|
||||
case .instance:
|
||||
break
|
||||
case .profileDirectory:
|
||||
eventsSubject.send(
|
||||
.navigation(.collection(identityContext
|
||||
.service
|
||||
.service(accountList: .directory(local: true),
|
||||
titleComponents: ["explore.profile-directory"]))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Mastodon
|
||||
import ServiceLayer
|
||||
|
||||
public final class InstanceViewModel: ObservableObject {
|
||||
|
@ -10,3 +11,7 @@ public final class InstanceViewModel: ObservableObject {
|
|||
self.instanceService = instanceService
|
||||
}
|
||||
}
|
||||
|
||||
public extension InstanceViewModel {
|
||||
var instance: Instance { instanceService.instance }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
final class InstanceCollectionViewCell: SeparatorConfiguredCollectionViewListCell {
|
||||
var viewModel: InstanceViewModel?
|
||||
|
||||
override func updateConfiguration(using state: UICellConfigurationState) {
|
||||
guard let viewModel = viewModel else { return }
|
||||
|
||||
contentConfiguration = InstanceContentConfiguration(viewModel: viewModel).updated(for: state)
|
||||
updateConstraintsIfNeeded()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
class SeparatorConfiguredCollectionViewListCell: UICollectionViewListCell {
|
||||
override func updateConstraints() {
|
||||
super.updateConstraints()
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
separatorLayoutGuide.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor),
|
||||
separatorLayoutGuide.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor)
|
||||
])
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
final class TagCollectionViewCell: UICollectionViewListCell {
|
||||
final class TagCollectionViewCell: SeparatorConfiguredCollectionViewListCell {
|
||||
var viewModel: TagViewModel?
|
||||
|
||||
override func updateConfiguration(using state: UICellConfigurationState) {
|
||||
|
@ -12,24 +12,4 @@ final class TagCollectionViewCell: UICollectionViewListCell {
|
|||
contentConfiguration = TagContentConfiguration(viewModel: viewModel).updated(for: state)
|
||||
updateConstraintsIfNeeded()
|
||||
}
|
||||
|
||||
override func updateConstraints() {
|
||||
super.updateConstraints()
|
||||
|
||||
let separatorLeadingAnchor: NSLayoutXAxisAnchor
|
||||
let separatorTrailingAnchor: NSLayoutXAxisAnchor
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
separatorLeadingAnchor = readableContentGuide.leadingAnchor
|
||||
separatorTrailingAnchor = readableContentGuide.trailingAnchor
|
||||
} else {
|
||||
separatorLeadingAnchor = leadingAnchor
|
||||
separatorTrailingAnchor = trailingAnchor
|
||||
}
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
separatorLayoutGuide.leadingAnchor.constraint(equalTo: separatorLeadingAnchor),
|
||||
separatorLayoutGuide.trailingAnchor.constraint(equalTo: separatorTrailingAnchor)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
struct InstanceContentConfiguration {
|
||||
let viewModel: InstanceViewModel
|
||||
}
|
||||
|
||||
extension InstanceContentConfiguration: UIContentConfiguration {
|
||||
func makeContentView() -> UIView & UIContentView {
|
||||
InstanceView(configuration: self)
|
||||
}
|
||||
|
||||
func updated(for state: UIConfigurationState) -> InstanceContentConfiguration {
|
||||
self
|
||||
}
|
||||
}
|
84
Views/UIKit/Content Views/InstanceView.swift
Normal file
84
Views/UIKit/Content Views/InstanceView.swift
Normal file
|
@ -0,0 +1,84 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import Kingfisher
|
||||
import UIKit
|
||||
|
||||
final class InstanceView: UIView {
|
||||
private let imageView = AnimatedImageView()
|
||||
private let titleLabel = UILabel()
|
||||
private let uriLabel = UILabel()
|
||||
private var instanceConfiguration: InstanceContentConfiguration
|
||||
|
||||
init(configuration: InstanceContentConfiguration) {
|
||||
instanceConfiguration = configuration
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
initialSetup()
|
||||
applyInstanceConfiguration()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
extension InstanceView: UIContentView {
|
||||
var configuration: UIContentConfiguration {
|
||||
get { instanceConfiguration }
|
||||
set {
|
||||
guard let instanceConfiguration = newValue as? InstanceContentConfiguration else { return }
|
||||
|
||||
self.instanceConfiguration = instanceConfiguration
|
||||
|
||||
applyInstanceConfiguration()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension InstanceView {
|
||||
func initialSetup() {
|
||||
let stackView = UIStackView()
|
||||
|
||||
addSubview(stackView)
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.axis = .vertical
|
||||
stackView.spacing = .defaultSpacing
|
||||
|
||||
stackView.addArrangedSubview(imageView)
|
||||
imageView.layer.cornerRadius = .defaultCornerRadius
|
||||
imageView.clipsToBounds = true
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
|
||||
stackView.addArrangedSubview(titleLabel)
|
||||
titleLabel.adjustsFontSizeToFitWidth = true
|
||||
titleLabel.font = .preferredFont(forTextStyle: .headline)
|
||||
titleLabel.numberOfLines = 0
|
||||
titleLabel.textAlignment = .center
|
||||
|
||||
stackView.addArrangedSubview(uriLabel)
|
||||
uriLabel.adjustsFontSizeToFitWidth = true
|
||||
uriLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||
uriLabel.numberOfLines = 0
|
||||
uriLabel.textAlignment = .center
|
||||
uriLabel.textColor = .secondaryLabel
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
stackView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
|
||||
stackView.topAnchor.constraint(equalTo: readableContentGuide.topAnchor),
|
||||
stackView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
|
||||
stackView.bottomAnchor.constraint(equalTo: readableContentGuide.bottomAnchor),
|
||||
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor, multiplier: 16 / 9)
|
||||
])
|
||||
}
|
||||
|
||||
func applyInstanceConfiguration() {
|
||||
let viewModel = instanceConfiguration.viewModel
|
||||
|
||||
imageView.kf.setImage(with: viewModel.instance.thumbnail)
|
||||
|
||||
titleLabel.text = viewModel.instance.title
|
||||
uriLabel.text = viewModel.instance.uri
|
||||
}
|
||||
}
|
|
@ -25,20 +25,21 @@ private extension ExploreSectionHeaderView {
|
|||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.adjustsFontForContentSizeCategory = true
|
||||
label.font = .preferredFont(forTextStyle: .headline)
|
||||
label.textColor = .secondaryLabel
|
||||
|
||||
let layoutGuide: UILayoutGuide
|
||||
let leadingConstraint: NSLayoutConstraint
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
layoutGuide = readableContentGuide
|
||||
leadingConstraint = label.leadingAnchor.constraint(equalToSystemSpacingAfter: leadingAnchor, multiplier: 1)
|
||||
} else {
|
||||
layoutGuide = layoutMarginsGuide
|
||||
leadingConstraint = label.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor)
|
||||
}
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
label.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor),
|
||||
label.topAnchor.constraint(equalTo: layoutGuide.topAnchor),
|
||||
label.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor),
|
||||
label.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor)
|
||||
leadingConstraint,
|
||||
label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
|
||||
label.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
|
||||
label.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue