Render tags
This commit is contained in:
parent
a7b2c849f9
commit
6b27cd1579
|
@ -452,7 +452,8 @@ public extension ContentDatabase {
|
||||||
.init(showContentToggled: $0.showContentToggled,
|
.init(showContentToggled: $0.showContentToggled,
|
||||||
showAttachmentsToggled: $0.showAttachmentsToggled))
|
showAttachmentsToggled: $0.showAttachmentsToggled))
|
||||||
},
|
},
|
||||||
titleLocalizedStringKey: "search.statuses")
|
titleLocalizedStringKey: "search.statuses"),
|
||||||
|
.init(items: results.hashtags.map(CollectionItem.tag), titleLocalizedStringKey: "search.tags")
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
|
|
@ -8,6 +8,7 @@ public enum CollectionItem: Hashable {
|
||||||
case account(Account)
|
case account(Account)
|
||||||
case notification(MastodonNotification, StatusConfiguration?)
|
case notification(MastodonNotification, StatusConfiguration?)
|
||||||
case conversation(Conversation)
|
case conversation(Conversation)
|
||||||
|
case tag(Tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension CollectionItem {
|
public extension CollectionItem {
|
||||||
|
@ -48,6 +49,8 @@ public extension CollectionItem {
|
||||||
return notification.id
|
return notification.id
|
||||||
case let .conversation(conversation):
|
case let .conversation(conversation):
|
||||||
return conversation.id
|
return conversation.id
|
||||||
|
case let .tag(tag):
|
||||||
|
return tag.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,8 @@ final class TableViewDataSource: UITableViewDiffableDataSource<CollectionSection
|
||||||
notificationListCell.viewModel = notificationViewModel
|
notificationListCell.viewModel = notificationViewModel
|
||||||
case let (conversationListCell as ConversationListCell, conversationViewModel as ConversationViewModel):
|
case let (conversationListCell as ConversationListCell, conversationViewModel as ConversationViewModel):
|
||||||
conversationListCell.viewModel = conversationViewModel
|
conversationListCell.viewModel = conversationViewModel
|
||||||
|
case let (tagTableViewCell as TagTableViewCell, tagViewModel as TagViewModel):
|
||||||
|
tagTableViewCell.viewModel = tagViewModel
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,8 @@ extension CollectionItem {
|
||||||
AccountListCell.self,
|
AccountListCell.self,
|
||||||
LoadMoreCell.self,
|
LoadMoreCell.self,
|
||||||
NotificationListCell.self,
|
NotificationListCell.self,
|
||||||
ConversationListCell.self]
|
ConversationListCell.self,
|
||||||
|
TagTableViewCell.self]
|
||||||
|
|
||||||
var cellClass: AnyClass {
|
var cellClass: AnyClass {
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -23,6 +24,8 @@ extension CollectionItem {
|
||||||
return statusConfiguration == nil ? NotificationListCell.self : StatusListCell.self
|
return statusConfiguration == nil ? NotificationListCell.self : StatusListCell.self
|
||||||
case .conversation:
|
case .conversation:
|
||||||
return ConversationListCell.self
|
return ConversationListCell.self
|
||||||
|
case .tag:
|
||||||
|
return TagTableViewCell.self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +52,8 @@ extension CollectionItem {
|
||||||
width: width,
|
width: width,
|
||||||
identification: identification,
|
identification: identification,
|
||||||
conversation: conversation)
|
conversation: conversation)
|
||||||
|
case let .tag(tag):
|
||||||
|
return TagView.estimatedHeight(width: width, tag: tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,22 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>tag.people-talking</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@people@</string>
|
||||||
|
<key>people</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>ld</string>
|
||||||
|
<key>one</key>
|
||||||
|
<string>%ld person talking</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>%ld people talking</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>status.poll.participation-count</key>
|
<key>status.poll.participation-count</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
|
|
@ -5,4 +5,13 @@ import Foundation
|
||||||
public struct Tag: Codable, Hashable {
|
public struct Tag: Codable, Hashable {
|
||||||
public let name: String
|
public let name: String
|
||||||
public let url: URL
|
public let url: URL
|
||||||
|
public let history: [History]?
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension Tag {
|
||||||
|
struct History: Codable, Hashable {
|
||||||
|
public let day: String
|
||||||
|
public let uses: String
|
||||||
|
public let accounts: String
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,6 +130,10 @@
|
||||||
D0CE9F87258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */; };
|
D0CE9F87258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */; };
|
||||||
D0CE9F88258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */; };
|
D0CE9F88258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */; };
|
||||||
D0D2AC3925BBEC0F003D5DF2 /* CollectionSection+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2AC3825BBEC0F003D5DF2 /* CollectionSection+Extensions.swift */; };
|
D0D2AC3925BBEC0F003D5DF2 /* CollectionSection+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2AC3825BBEC0F003D5DF2 /* CollectionSection+Extensions.swift */; };
|
||||||
|
D0D2AC4725BCD289003D5DF2 /* TagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2AC4625BCD289003D5DF2 /* TagView.swift */; };
|
||||||
|
D0D2AC4D25BCD2A9003D5DF2 /* TagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2AC4C25BCD2A9003D5DF2 /* TagTableViewCell.swift */; };
|
||||||
|
D0D2AC5325BCD2BA003D5DF2 /* TagContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2AC5225BCD2BA003D5DF2 /* TagContentConfiguration.swift */; };
|
||||||
|
D0D2AC6725BD0484003D5DF2 /* LineChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2AC6625BD0484003D5DF2 /* LineChartView.swift */; };
|
||||||
D0DD50CB256B1F24004A04F7 /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DD50CA256B1F24004A04F7 /* ReportView.swift */; };
|
D0DD50CB256B1F24004A04F7 /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DD50CA256B1F24004A04F7 /* ReportView.swift */; };
|
||||||
D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E1F582251F13EC00D45315 /* WebfingerIndicatorView.swift */; };
|
D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E1F582251F13EC00D45315 /* WebfingerIndicatorView.swift */; };
|
||||||
D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0E2C1D024FD97F000854680 /* ViewModels */; };
|
D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0E2C1D024FD97F000854680 /* ViewModels */; };
|
||||||
|
@ -303,6 +307,10 @@
|
||||||
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; };
|
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentUploadView.swift; sourceTree = "<group>"; };
|
D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentUploadView.swift; sourceTree = "<group>"; };
|
||||||
D0D2AC3825BBEC0F003D5DF2 /* CollectionSection+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CollectionSection+Extensions.swift"; sourceTree = "<group>"; };
|
D0D2AC3825BBEC0F003D5DF2 /* CollectionSection+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CollectionSection+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
|
D0D2AC4625BCD289003D5DF2 /* TagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagView.swift; sourceTree = "<group>"; };
|
||||||
|
D0D2AC4C25BCD2A9003D5DF2 /* TagTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
D0D2AC5225BCD2BA003D5DF2 /* TagContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagContentConfiguration.swift; sourceTree = "<group>"; };
|
||||||
|
D0D2AC6625BD0484003D5DF2 /* LineChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChartView.swift; sourceTree = "<group>"; };
|
||||||
D0D7C013250440610039AD6F /* CodableBloomFilter */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CodableBloomFilter; sourceTree = "<group>"; };
|
D0D7C013250440610039AD6F /* CodableBloomFilter */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CodableBloomFilter; sourceTree = "<group>"; };
|
||||||
D0DD50CA256B1F24004A04F7 /* ReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportView.swift; sourceTree = "<group>"; };
|
D0DD50CA256B1F24004A04F7 /* ReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportView.swift; sourceTree = "<group>"; };
|
||||||
D0E0F1E424FC49FC002C04BF /* Mastodon */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Mastodon; sourceTree = "<group>"; };
|
D0E0F1E424FC49FC002C04BF /* Mastodon */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Mastodon; sourceTree = "<group>"; };
|
||||||
|
@ -509,6 +517,7 @@
|
||||||
D07EC7F125B13E57006DF726 /* EmojiView.swift */,
|
D07EC7F125B13E57006DF726 /* EmojiView.swift */,
|
||||||
D0BEB20424FA1107001B0F04 /* FiltersView.swift */,
|
D0BEB20424FA1107001B0F04 /* FiltersView.swift */,
|
||||||
D0C7D42224F76169001EBDBB /* IdentitiesView.swift */,
|
D0C7D42224F76169001EBDBB /* IdentitiesView.swift */,
|
||||||
|
D0D2AC6625BD0484003D5DF2 /* LineChartView.swift */,
|
||||||
D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */,
|
D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */,
|
||||||
D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */,
|
D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */,
|
||||||
D0B8510B25259E56004E0744 /* LoadMoreCell.swift */,
|
D0B8510B25259E56004E0744 /* LoadMoreCell.swift */,
|
||||||
|
@ -537,6 +546,9 @@
|
||||||
D04226FC2546AC0B000980A3 /* StartupAndSyncingPreferencesView.swift */,
|
D04226FC2546AC0B000980A3 /* StartupAndSyncingPreferencesView.swift */,
|
||||||
D0625E55250F086B00502611 /* Status */,
|
D0625E55250F086B00502611 /* Status */,
|
||||||
D0C7D42524F76169001EBDBB /* TableView.swift */,
|
D0C7D42524F76169001EBDBB /* TableView.swift */,
|
||||||
|
D0D2AC5225BCD2BA003D5DF2 /* TagContentConfiguration.swift */,
|
||||||
|
D0D2AC4C25BCD2A9003D5DF2 /* TagTableViewCell.swift */,
|
||||||
|
D0D2AC4625BCD289003D5DF2 /* TagView.swift */,
|
||||||
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */,
|
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */,
|
||||||
D0EA59472522B8B600804347 /* ViewConstants.swift */,
|
D0EA59472522B8B600804347 /* ViewConstants.swift */,
|
||||||
D0F2D54A2581CF7D00986197 /* VisualEffectBlur.swift */,
|
D0F2D54A2581CF7D00986197 /* VisualEffectBlur.swift */,
|
||||||
|
@ -882,6 +894,8 @@
|
||||||
D00702362555F4C500F38136 /* ConversationContentConfiguration.swift in Sources */,
|
D00702362555F4C500F38136 /* ConversationContentConfiguration.swift in Sources */,
|
||||||
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */,
|
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */,
|
||||||
D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */,
|
D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */,
|
||||||
|
D0D2AC4D25BCD2A9003D5DF2 /* TagTableViewCell.swift in Sources */,
|
||||||
|
D0D2AC5325BCD2BA003D5DF2 /* TagContentConfiguration.swift in Sources */,
|
||||||
D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
||||||
D08B8D72254246E200B1EBEF /* PollView.swift in Sources */,
|
D08B8D72254246E200B1EBEF /* PollView.swift in Sources */,
|
||||||
D035F8A925B9155900DC75ED /* NewStatusButtonView.swift in Sources */,
|
D035F8A925B9155900DC75ED /* NewStatusButtonView.swift in Sources */,
|
||||||
|
@ -894,6 +908,7 @@
|
||||||
D036AA17254CA824009094DF /* StatusBodyView.swift in Sources */,
|
D036AA17254CA824009094DF /* StatusBodyView.swift in Sources */,
|
||||||
D035F86F25B7F30E00DC75ED /* MainNavigationView.swift in Sources */,
|
D035F86F25B7F30E00DC75ED /* MainNavigationView.swift in Sources */,
|
||||||
D08E512125786A6600FA2C5F /* UIButton+Extensions.swift in Sources */,
|
D08E512125786A6600FA2C5F /* UIButton+Extensions.swift in Sources */,
|
||||||
|
D0D2AC4725BCD289003D5DF2 /* TagView.swift in Sources */,
|
||||||
D05936F425AA66A600754FDF /* UIView+Extensions.swift in Sources */,
|
D05936F425AA66A600754FDF /* UIView+Extensions.swift in Sources */,
|
||||||
D05936E925AA3F3D00754FDF /* EditAttachmentView.swift in Sources */,
|
D05936E925AA3F3D00754FDF /* EditAttachmentView.swift in Sources */,
|
||||||
D035F8C725B96A4000DC75ED /* SecondaryNavigationButton.swift in Sources */,
|
D035F8C725B96A4000DC75ED /* SecondaryNavigationButton.swift in Sources */,
|
||||||
|
@ -918,6 +933,7 @@
|
||||||
D0FCC105259C4E61000B67DF /* NewStatusViewController.swift in Sources */,
|
D0FCC105259C4E61000B67DF /* NewStatusViewController.swift in Sources */,
|
||||||
D0F2D54B2581CF7D00986197 /* VisualEffectBlur.swift in Sources */,
|
D0F2D54B2581CF7D00986197 /* VisualEffectBlur.swift in Sources */,
|
||||||
D087671625BAA8C0001FDD43 /* ExploreViewController.swift in Sources */,
|
D087671625BAA8C0001FDD43 /* ExploreViewController.swift in Sources */,
|
||||||
|
D0D2AC6725BD0484003D5DF2 /* LineChartView.swift in Sources */,
|
||||||
D0A7AC7325748BFF00E4E8AB /* ReportStatusView.swift in Sources */,
|
D0A7AC7325748BFF00E4E8AB /* ReportStatusView.swift in Sources */,
|
||||||
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
|
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
|
||||||
D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */,
|
D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */,
|
||||||
|
|
|
@ -86,6 +86,10 @@ public extension NavigationService {
|
||||||
mastodonAPIClient: mastodonAPIClient,
|
mastodonAPIClient: mastodonAPIClient,
|
||||||
contentDatabase: contentDatabase)
|
contentDatabase: contentDatabase)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func timelineService(timeline: Timeline) -> TimelineService {
|
||||||
|
TimelineService(timeline: timeline, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension NavigationService {
|
private extension NavigationService {
|
||||||
|
|
|
@ -154,6 +154,11 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
||||||
.navigation(.collection(collectionService
|
.navigation(.collection(collectionService
|
||||||
.navigationService
|
.navigationService
|
||||||
.contextService(id: status.displayStatus.id))))
|
.contextService(id: status.displayStatus.id))))
|
||||||
|
case let .tag(tag):
|
||||||
|
eventsSubject.send(
|
||||||
|
.navigation(.collection(collectionService
|
||||||
|
.navigationService
|
||||||
|
.timelineService(timeline: .tag(tag.name)))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,6 +260,16 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
||||||
|
|
||||||
cache(viewModel: viewModel, forItem: item)
|
cache(viewModel: viewModel, forItem: item)
|
||||||
|
|
||||||
|
return viewModel
|
||||||
|
case let .tag(tag):
|
||||||
|
if let cachedViewModel = cachedViewModel {
|
||||||
|
return cachedViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
let viewModel = TagViewModel(tag: tag)
|
||||||
|
|
||||||
|
cache(viewModel: viewModel, forItem: item)
|
||||||
|
|
||||||
return viewModel
|
return viewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
import Mastodon
|
||||||
|
|
||||||
|
public struct TagViewModel: CollectionItemViewModel {
|
||||||
|
public let events: AnyPublisher<AnyPublisher<CollectionItemEvent, Error>, Never>
|
||||||
|
|
||||||
|
private let tag: Tag
|
||||||
|
|
||||||
|
init(tag: Tag) {
|
||||||
|
self.tag = tag
|
||||||
|
events = Empty().eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension TagViewModel {
|
||||||
|
var name: String { "#".appending(tag.name) }
|
||||||
|
|
||||||
|
var accounts: Int? {
|
||||||
|
guard let history = tag.history,
|
||||||
|
let accountsString = history.first?.accounts,
|
||||||
|
var accounts = Int(accountsString)
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
|
if history.count > 1, let secondDayAccounts = Int(history[1].accounts) {
|
||||||
|
accounts += secondDayAccounts
|
||||||
|
}
|
||||||
|
|
||||||
|
return accounts
|
||||||
|
}
|
||||||
|
|
||||||
|
var uses: Int? {
|
||||||
|
guard let history = tag.history,
|
||||||
|
let usesString = history.first?.uses,
|
||||||
|
var uses = Int(usesString)
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
|
if history.count > 1, let secondDayUses = Int(history[1].uses) {
|
||||||
|
uses += secondDayUses
|
||||||
|
}
|
||||||
|
|
||||||
|
return uses
|
||||||
|
}
|
||||||
|
|
||||||
|
var usageHistory: [Int] {
|
||||||
|
tag.history?.compactMap { Int($0.uses) } ?? []
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
final class LineChartView: UIView {
|
||||||
|
var values = [Int]() {
|
||||||
|
didSet { setNeedsDisplay() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
backgroundColor = .clear
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
UIView.layoutFittingExpandedSize
|
||||||
|
}
|
||||||
|
|
||||||
|
override func draw(_ rect: CGRect) {
|
||||||
|
let path = UIBezierPath()
|
||||||
|
|
||||||
|
path.lineWidth = Self.lineWidth
|
||||||
|
path.lineCapStyle = .round
|
||||||
|
|
||||||
|
let valueCount = values.count
|
||||||
|
|
||||||
|
guard valueCount > 0, let maxValue = values.max() else { return }
|
||||||
|
|
||||||
|
for (index, value) in values.enumerated() {
|
||||||
|
let x = CGFloat(index) / CGFloat(valueCount) * rect.width
|
||||||
|
let y = rect.height - CGFloat(value) / max(CGFloat(maxValue), CGFloat(0).nextUp) * rect.height
|
||||||
|
let point = CGPoint(
|
||||||
|
x: min(max(x, Self.lineWidth / 2), rect.width - Self.lineWidth / 2),
|
||||||
|
y: min(max(y, Self.lineWidth / 2), rect.height - Self.lineWidth / 2))
|
||||||
|
|
||||||
|
if index > 0 {
|
||||||
|
path.addLine(to: point)
|
||||||
|
}
|
||||||
|
|
||||||
|
path.move(to: point)
|
||||||
|
}
|
||||||
|
|
||||||
|
path.close()
|
||||||
|
UIColor.link.setStroke()
|
||||||
|
path.stroke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension LineChartView {
|
||||||
|
static let lineWidth: CGFloat = 2
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
|
struct TagContentConfiguration {
|
||||||
|
let viewModel: TagViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TagContentConfiguration: UIContentConfiguration {
|
||||||
|
func makeContentView() -> UIView & UIContentView {
|
||||||
|
TagView(configuration: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updated(for state: UIConfigurationState) -> TagContentConfiguration {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
|
final class TagTableViewCell: UITableViewCell {
|
||||||
|
var viewModel: TagViewModel?
|
||||||
|
|
||||||
|
override func updateConfiguration(using state: UICellConfigurationState) {
|
||||||
|
guard let viewModel = viewModel else { return }
|
||||||
|
|
||||||
|
contentConfiguration = TagContentConfiguration(viewModel: viewModel).updated(for: state)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||||
|
separatorInset.left = 0
|
||||||
|
separatorInset.right = 0
|
||||||
|
} else {
|
||||||
|
separatorInset.left = layoutMargins.left
|
||||||
|
separatorInset.right = layoutMargins.right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Mastodon
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
final class TagView: UIView {
|
||||||
|
private let nameLabel = UILabel()
|
||||||
|
private let accountsLabel = UILabel()
|
||||||
|
private let usesLabel = UILabel()
|
||||||
|
private let lineChartView = LineChartView()
|
||||||
|
private var tagConfiguration: TagContentConfiguration
|
||||||
|
|
||||||
|
init(configuration: TagContentConfiguration) {
|
||||||
|
tagConfiguration = configuration
|
||||||
|
|
||||||
|
super.init(frame: .zero)
|
||||||
|
|
||||||
|
initialSetup()
|
||||||
|
applyTagConfiguration()
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TagView {
|
||||||
|
static func estimatedHeight(width: CGFloat, tag: Tag) -> CGFloat {
|
||||||
|
UITableView.automaticDimension
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TagView: UIContentView {
|
||||||
|
var configuration: UIContentConfiguration {
|
||||||
|
get { tagConfiguration }
|
||||||
|
set {
|
||||||
|
guard let tagConfiguration = newValue as? TagContentConfiguration else { return }
|
||||||
|
|
||||||
|
self.tagConfiguration = tagConfiguration
|
||||||
|
|
||||||
|
applyTagConfiguration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension TagView {
|
||||||
|
func initialSetup() {
|
||||||
|
let stackView = UIStackView()
|
||||||
|
|
||||||
|
addSubview(stackView)
|
||||||
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
stackView.spacing = .defaultSpacing
|
||||||
|
|
||||||
|
let verticalStackView = UIStackView()
|
||||||
|
|
||||||
|
stackView.addArrangedSubview(verticalStackView)
|
||||||
|
verticalStackView.axis = .vertical
|
||||||
|
verticalStackView.spacing = .compactSpacing
|
||||||
|
|
||||||
|
verticalStackView.addArrangedSubview(nameLabel)
|
||||||
|
nameLabel.adjustsFontForContentSizeCategory = true
|
||||||
|
nameLabel.font = .preferredFont(forTextStyle: .headline)
|
||||||
|
|
||||||
|
verticalStackView.addArrangedSubview(accountsLabel)
|
||||||
|
accountsLabel.adjustsFontForContentSizeCategory = true
|
||||||
|
accountsLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||||
|
accountsLabel.textColor = .secondaryLabel
|
||||||
|
|
||||||
|
stackView.addArrangedSubview(UIView())
|
||||||
|
|
||||||
|
stackView.addArrangedSubview(usesLabel)
|
||||||
|
usesLabel.adjustsFontForContentSizeCategory = true
|
||||||
|
usesLabel.font = .preferredFont(forTextStyle: .largeTitle)
|
||||||
|
usesLabel.setContentHuggingPriority(.required, for: .vertical)
|
||||||
|
|
||||||
|
stackView.addArrangedSubview(lineChartView)
|
||||||
|
|
||||||
|
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),
|
||||||
|
lineChartView.heightAnchor.constraint(equalTo: usesLabel.heightAnchor),
|
||||||
|
lineChartView.widthAnchor.constraint(equalTo: lineChartView.heightAnchor, multiplier: 16 / 9)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyTagConfiguration() {
|
||||||
|
let viewModel = tagConfiguration.viewModel
|
||||||
|
|
||||||
|
nameLabel.text = viewModel.name
|
||||||
|
|
||||||
|
if let accounts = viewModel.accounts {
|
||||||
|
accountsLabel.text = String.localizedStringWithFormat(
|
||||||
|
NSLocalizedString("tag.people-talking", comment: ""),
|
||||||
|
accounts)
|
||||||
|
accountsLabel.isHidden = false
|
||||||
|
} else {
|
||||||
|
accountsLabel.isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if let uses = viewModel.uses {
|
||||||
|
usesLabel.text = String(uses)
|
||||||
|
usesLabel.isHidden = false
|
||||||
|
} else {
|
||||||
|
usesLabel.isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
lineChartView.values = viewModel.usageHistory.reversed()
|
||||||
|
lineChartView.isHidden = viewModel.usageHistory.isEmpty
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue