From 4ed862120c5524e61b467e3a8e139573e05b8be4 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Thu, 28 Jan 2021 23:20:25 -0500 Subject: [PATCH] Add trending hashtags to add saved hashtag controller --- Tusker.xcodeproj/project.pbxproj | 28 ++++ .../AddSavedHashtagViewController.swift | 97 +++++++++++-- .../HashtagSearchResultsViewController.swift | 29 ++++ .../Search/SearchResultsViewController.swift | 7 +- .../Hashtag Cell/HashtagHistoryView.swift | 133 ++++++++++++++++++ .../Hashtag Cell/HashtagTableViewCell.xib | 9 +- .../TrendingHashtagTableViewCell.swift | 41 ++++++ .../TrendingHashtagTableViewCell.xib | 73 ++++++++++ Tusker/en.lproj/Localizable.stringsdict | 33 +++++ 9 files changed, 428 insertions(+), 22 deletions(-) create mode 100644 Tusker/Screens/Explore/HashtagSearchResultsViewController.swift create mode 100644 Tusker/Views/Hashtag Cell/HashtagHistoryView.swift create mode 100644 Tusker/Views/Hashtag Cell/TrendingHashtagTableViewCell.swift create mode 100644 Tusker/Views/Hashtag Cell/TrendingHashtagTableViewCell.xib create mode 100644 Tusker/en.lproj/Localizable.stringsdict diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index b3028765..9bca20f6 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -18,6 +18,10 @@ 04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; }; 04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; }; 04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; }; + D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */; }; + D6093FB025BE0B01004811E6 /* TrendingHashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */; }; + D6093FB125BE0B01004811E6 /* TrendingHashtagTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */; }; + D6093FB725BE0CF3004811E6 /* HashtagHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FB625BE0CF3004811E6 /* HashtagHistoryView.swift */; }; D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = D60CFFDA24A290BA00D00083 /* SwiftSoup */; }; D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; }; D60E2F272442372B005F8713 /* StatusMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F232442372B005F8713 /* StatusMO.swift */; }; @@ -296,6 +300,7 @@ D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */; }; D6E426B325337C7000C02E1C /* CustomEmojiImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */; }; D6E426B9253382B300C02E1C /* SearchResultType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426B8253382B300C02E1C /* SearchResultType.swift */; }; + D6E57FA325C26FAB00341037 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = D6E57FA525C26FAB00341037 /* Localizable.stringsdict */; }; D6E6F26321603F8B006A8599 /* CharacterCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E6F26221603F8B006A8599 /* CharacterCounter.swift */; }; D6E6F26521604242006A8599 /* CharacterCounterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E6F26421604242006A8599 /* CharacterCounterTests.swift */; }; D6EAE0DB2550CC8A002DB0AC /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */; }; @@ -376,6 +381,10 @@ 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarViewController.swift; sourceTree = ""; }; 04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; 04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = ""; }; + D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagSearchResultsViewController.swift; sourceTree = ""; }; + D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagTableViewCell.swift; sourceTree = ""; }; + D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TrendingHashtagTableViewCell.xib; sourceTree = ""; }; + D6093FB625BE0CF3004811E6 /* HashtagHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagHistoryView.swift; sourceTree = ""; }; D60A4FFB238B726A008AC647 /* StatusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusState.swift; sourceTree = ""; }; D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseStatusTableViewCell.swift; sourceTree = ""; }; D60E2F232442372B005F8713 /* StatusMO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMO.swift; sourceTree = ""; }; @@ -660,6 +669,7 @@ D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiImageView.swift; sourceTree = ""; }; D6E426B8253382B300C02E1C /* SearchResultType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultType.swift; sourceTree = ""; }; D6E4885C24A2890C0011C13E /* Tusker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Tusker.entitlements; sourceTree = ""; }; + D6E57FA425C26FAB00341037 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; D6E6F26221603F8B006A8599 /* CharacterCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterCounter.swift; sourceTree = ""; }; D6E6F26421604242006A8599 /* CharacterCounterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterCounterTests.swift; sourceTree = ""; }; D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusableTextField.swift; sourceTree = ""; }; @@ -834,6 +844,9 @@ children = ( D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */, D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */, + D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */, + D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */, + D6093FB625BE0CF3004811E6 /* HashtagHistoryView.swift */, ); path = "Hashtag Cell"; sourceTree = ""; @@ -901,6 +914,7 @@ children = ( D627943D23A564D400D38C68 /* ExploreViewController.swift */, D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */, + D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */, D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */, ); path = Explore; @@ -1448,6 +1462,7 @@ D6CA6A91249FAD8900AD45C1 /* AudioSessionHelper.swift */, D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */, D6B30E08254BAF63009CAEE5 /* ImageGrayscalifier.swift */, + D6E57FA525C26FAB00341037 /* Localizable.stringsdict */, D67B506B250B28FF00FAECFB /* Vendor */, D6F1F84E2193B9BE00F5FE67 /* Caching */, D6757A7A2157E00100721E32 /* XCallbackURL */, @@ -1732,12 +1747,14 @@ D6A3BC7D232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib in Resources */, D6412B0B24B0D4C600F5412E /* ProfileHeaderView.xib in Resources */, D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */, + D6E57FA325C26FAB00341037 /* Localizable.stringsdict in Resources */, D6B053AC23BD2F1400A066FA /* AssetCollectionViewCell.xib in Resources */, D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */, D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */, D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */, D6969EA4240DD28D002843CE /* UnknownNotificationTableViewCell.xib in Resources */, D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */, + D6093FB125BE0B01004811E6 /* TrendingHashtagTableViewCell.xib in Resources */, D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */, D626493323BD751600612E6E /* ShowCameraCollectionViewCell.xib in Resources */, D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */, @@ -1865,6 +1882,7 @@ D60E2F292442372B005F8713 /* AccountMO.swift in Sources */, D6412B0324AFF6A600F5412E /* TabBarScrollableViewController.swift in Sources */, D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */, + D6093FB725BE0CF3004811E6 /* HashtagHistoryView.swift in Sources */, D6EBF01723C55E0D00AE061B /* UISceneSession+MastodonController.swift in Sources */, 04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */, D68E525D24A3E8F00054355A /* SearchViewController.swift in Sources */, @@ -1956,6 +1974,7 @@ D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */, D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */, D6EAE0DB2550CC8A002DB0AC /* FocusableTextField.swift in Sources */, + D6093FB025BE0B01004811E6 /* TrendingHashtagTableViewCell.swift in Sources */, D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */, D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */, D6EE63FB2551F7F60065485C /* StatusCollapseButton.swift in Sources */, @@ -2054,6 +2073,7 @@ D626493F23C101C500612E6E /* AlbumAssetCollectionViewController.swift in Sources */, D68015422401A74600D6103B /* MediaPrefsView.swift in Sources */, D6C99FCB24FADC91005C74D3 /* MainComposeTextView.swift in Sources */, + D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */, D6E4267725327FB400C02E1C /* ComposeAutocompleteView.swift in Sources */, D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */, D677284E24ECC01D00C732D3 /* Draft.swift in Sources */, @@ -2133,6 +2153,14 @@ name = LaunchScreen.storyboard; sourceTree = ""; }; + D6E57FA525C26FAB00341037 /* Localizable.stringsdict */ = { + isa = PBXVariantGroup; + children = ( + D6E57FA425C26FAB00341037 /* en */, + ); + name = Localizable.stringsdict; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ diff --git a/Tusker/Screens/Explore/AddSavedHashtagViewController.swift b/Tusker/Screens/Explore/AddSavedHashtagViewController.swift index be1b031e..5bdc5c31 100644 --- a/Tusker/Screens/Explore/AddSavedHashtagViewController.swift +++ b/Tusker/Screens/Explore/AddSavedHashtagViewController.swift @@ -9,12 +9,19 @@ import UIKit import Pachyderm -class AddSavedHashtagViewController: SearchResultsViewController { +class AddSavedHashtagViewController: EnhancedTableViewController { + weak var mastodonController: MastodonController! + + var resultsController: SearchResultsViewController! var searchController: UISearchController! + var dataSource: UITableViewDiffableDataSource! + init(mastodonController: MastodonController) { - super.init(mastodonController: mastodonController, resultTypes: [.hashtags]) + self.mastodonController = mastodonController + + super.init(style: .grouped) } required init?(coder: NSCoder) { @@ -24,14 +31,32 @@ class AddSavedHashtagViewController: SearchResultsViewController { override func viewDidLoad() { super.viewDidLoad() - delegate = self + title = NSLocalizedString("Search", comment: "search screen title") - searchController = UISearchController(searchResultsController: nil) - searchController.obscuresBackgroundDuringPresentation = false + tableView.register(UINib(nibName: "TrendingHashtagTableViewCell", bundle: .main), forCellReuseIdentifier: "trendingTagCell") + tableView.rowHeight = 44 + + dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in + switch item { + case let .tag(hashtag): + let cell = tableView.dequeueReusableCell(withIdentifier: "trendingTagCell", for: indexPath) as! TrendingHashtagTableViewCell + cell.updateUI(hashtag: hashtag) + return cell + } + }) + + resultsController = HashtagSearchResultsViewController(mastodonController: mastodonController) + resultsController.delegate = self + resultsController.exploreNavigationController = self.navigationController! + + searchController = UISearchController(searchResultsController: resultsController) + searchController.obscuresBackgroundDuringPresentation = true searchController.hidesNavigationBarDuringPresentation = false + searchController.searchResultsUpdater = resultsController searchController.searchBar.autocapitalizationType = .none searchController.searchBar.placeholder = NSLocalizedString("Search for hashtags to save", comment: "add saved hashtag search field placeholder") - searchController.searchBar.delegate = self + searchController.searchBar.delegate = resultsController + searchController.searchBar.showsCancelButton = false definesPresentationContext = true @@ -41,11 +66,38 @@ class AddSavedHashtagViewController: SearchResultsViewController { navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonPressed)) } - override func performSearch(query: String?) { - if let query = query, !query.starts(with: "#") { - super.performSearch(query: "#\(query)") - } else { - super.performSearch(query: query) + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + let request = Client.getTrends(limit: 10) + mastodonController.run(request) { (response) in + var snapshot = NSDiffableDataSourceSnapshot() + + guard case let .success(hashtags, _) = response, + hashtags.count > 0 else { + self.dataSource.apply(snapshot) + return + } + + snapshot.appendSections([.trendingTags]) + snapshot.appendItems(hashtags.map { .tag($0) }) + self.dataSource.apply(snapshot, animatingDifferences: false) + } + } + + private func selectHashtag(_ hashtag: Hashtag) { + SavedDataManager.shared.add(hashtag: hashtag, for: mastodonController.accountInfo!) + presentingViewController!.dismiss(animated: true) + } + + // MARK: - Table View Delegate + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + switch dataSource.itemIdentifier(for: indexPath) { + case nil: + return + case let .tag(hashtag): + selectHashtag(hashtag) } } @@ -57,9 +109,24 @@ class AddSavedHashtagViewController: SearchResultsViewController { } -extension AddSavedHashtagViewController: SearchResultsViewControllerDelegate { - func selectedSearchResult(hashtag: Hashtag) { - SavedDataManager.shared.add(hashtag: hashtag, for: mastodonController.accountInfo!) - dismiss(animated: true) +extension AddSavedHashtagViewController { + enum Section { + case trendingTags + } + + enum Item: Hashable { + case tag(Hashtag) + } + + class DataSource: UITableViewDiffableDataSource { + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return NSLocalizedString("Trending Hashtags", comment: "trending hashtags seciton title") + } + } +} + +extension AddSavedHashtagViewController: SearchResultsViewControllerDelegate { + func selectedSearchResult(hashtag: Hashtag) { + selectHashtag(hashtag) } } diff --git a/Tusker/Screens/Explore/HashtagSearchResultsViewController.swift b/Tusker/Screens/Explore/HashtagSearchResultsViewController.swift new file mode 100644 index 00000000..7ed96e5b --- /dev/null +++ b/Tusker/Screens/Explore/HashtagSearchResultsViewController.swift @@ -0,0 +1,29 @@ +// +// HashtagSearchResultsViewController.swift +// Tusker +// +// Created by Shadowfacts on 1/24/21. +// Copyright © 2021 Shadowfacts. All rights reserved. +// + +import UIKit + +class HashtagSearchResultsViewController: SearchResultsViewController { + + init(mastodonController: MastodonController) { + super.init(mastodonController: mastodonController, resultTypes: [.hashtags]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func performSearch(query: String?) { + if let query = query, !query.starts(with: "#") { + super.performSearch(query: "#\(query)") + } else { + super.performSearch(query: query) + } + } + +} diff --git a/Tusker/Screens/Search/SearchResultsViewController.swift b/Tusker/Screens/Search/SearchResultsViewController.swift index be8c0c4f..75e7f2b0 100644 --- a/Tusker/Screens/Search/SearchResultsViewController.swift +++ b/Tusker/Screens/Search/SearchResultsViewController.swift @@ -132,6 +132,7 @@ class SearchResultsViewController: EnhancedTableViewController { activityIndicator.isHidden = false activityIndicator.startAnimating() + let resultTypes = self.resultTypes let request = Client.search(query: query, types: resultTypes, resolve: true, limit: 10) mastodonController.run(request) { (response) in guard case let .success(results, _) = response else { fatalError() } @@ -161,16 +162,16 @@ class SearchResultsViewController: EnhancedTableViewController { } } - if !results.accounts.isEmpty { + if !results.accounts.isEmpty && (resultTypes == nil || resultTypes!.contains(.accounts)) { snapshot.appendSections([.accounts]) snapshot.appendItems(results.accounts.map { .account($0.id) }, toSection: .accounts) addAccounts(results.accounts) } - if !results.hashtags.isEmpty { + if !results.hashtags.isEmpty && (resultTypes == nil || resultTypes!.contains(.hashtags)) { snapshot.appendSections([.hashtags]) snapshot.appendItems(results.hashtags.map { .hashtag($0) }, toSection: .hashtags) } - if !results.statuses.isEmpty { + if !results.statuses.isEmpty && (resultTypes == nil || resultTypes!.contains(.statuses)) { snapshot.appendSections([.statuses]) snapshot.appendItems(results.statuses.map { .status($0.id, .unknown) }, toSection: .statuses) addStatuses(results.statuses) diff --git a/Tusker/Views/Hashtag Cell/HashtagHistoryView.swift b/Tusker/Views/Hashtag Cell/HashtagHistoryView.swift new file mode 100644 index 00000000..90226d84 --- /dev/null +++ b/Tusker/Views/Hashtag Cell/HashtagHistoryView.swift @@ -0,0 +1,133 @@ +// +// HashtagHistoryView.swift +// Tusker +// +// Created by Shadowfacts on 1/24/21. +// Copyright © 2021 Shadowfacts. All rights reserved. +// + +import UIKit +import Pachyderm + +class HashtagHistoryView: UIView { + + private var history: [Hashtag.History]? + + private let curveRadius: CGFloat = 10 + + override func layoutSubviews() { + super.layoutSubviews() + + createLayers() + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + createLayers() + } + + func setHistory(_ history: [Hashtag.History]?) { + if let history = history { + self.history = history.sorted(by: { $0.day < $1.day }) + } else { + self.history = nil + } + + createLayers() + } + + private func createLayers() { + guard let history = history, + history.count >= 2 else { return } + + let maxUses = history.max(by: { $0.uses < $1.uses })!.uses + + // remove old layers if this view is being re-used + layer.sublayers?.forEach { $0.removeFromSuperlayer() } + + let path = UIBezierPath() + + let widthStep = bounds.width / CGFloat(history.count - 1) + + let points: [CGPoint] = history.enumerated().map { (index, entry) in + let x = CGFloat(index) * widthStep + let yFrac = CGFloat(entry.uses) / CGFloat(maxUses) + let y = (1 - yFrac) * bounds.height + return CGPoint(x: x, y: y) + } + + var gapStartPoints = [CGPoint]() + var gapEndPoints = [CGPoint]() + + for (index, point) in points.enumerated().dropFirst().dropLast() { + let prev = points[index - 1] + let next = points[index + 1] + + let a = atan((point.y - prev.y) / widthStep) + let b = atan((next.y - point.y) / widthStep) + let innerAngle = .pi - a - b + + let gapDistance = curveRadius / sin(innerAngle / 2) + + let x1 = point.x - cos(a) * gapDistance + let y1 = point.y - sin(a) * gapDistance + gapStartPoints.append(CGPoint(x: x1, y: y1)) + + let x2 = point.x + cos(b) * gapDistance + let y2 = point.y + sin(b) * gapDistance + gapEndPoints.append(CGPoint(x: x2, y: y2)) + } + + path.move(to: points.first!) + for (index, point) in points.dropFirst().dropLast().enumerated() { + path.addLine(to: gapStartPoints[index]) + path.addQuadCurve(to: gapEndPoints[index], controlPoint: point) + } + path.addLine(to: points.last!) + + let borderLayer = CAShapeLayer() + // copy the border path so we can continue mutating the UIBezierPath to create the fill path + borderLayer.path = path.cgPath.copy()! + borderLayer.strokeColor = tintColor.cgColor + borderLayer.fillColor = nil + borderLayer.lineWidth = 2 + borderLayer.lineCap = .round + + path.addLine(to: CGPoint(x: bounds.width, y: bounds.height)) + path.addLine(to: CGPoint(x: 0, y: bounds.height)) + path.addLine(to: points.first!) + + let fillLayer = CAShapeLayer() + fillLayer.path = path.cgPath + let fillColor = self.fillColor() + fillLayer.strokeColor = fillColor + fillLayer.fillColor = fillColor + fillLayer.lineWidth = 2 + + layer.addSublayer(fillLayer) + layer.addSublayer(borderLayer) + } + + // The non-transparent fill color. + // We blend with the view's background color ourselves so that final color is non-transparent, + // otherwise when the fill layer's border and fill overlap, there's a visibly darker patch + // because transparent colors are being blended together. + private func fillColor() -> CGColor { + var backgroundRed: CGFloat = 0 + var backgroundGreen: CGFloat = 0 + var backgroundBlue: CGFloat = 0 + var tintRed: CGFloat = 0 + var tintGreen: CGFloat = 0 + var tintBlue: CGFloat = 0 + traitCollection.performAsCurrent { + backgroundColor!.getRed(&backgroundRed, green: &backgroundGreen, blue: &backgroundBlue, alpha: nil) + tintColor.getRed(&tintRed, green: &tintGreen, blue: &tintBlue, alpha: nil) + } + let blendedRed = (backgroundRed + tintRed) / 2 + let blendedGreen = (backgroundGreen + tintGreen) / 2 + let blendedBlue = (backgroundBlue + tintBlue) / 2 + return CGColor(red: blendedRed, green: blendedGreen, blue: blendedBlue, alpha: 1) + } + +} diff --git a/Tusker/Views/Hashtag Cell/HashtagTableViewCell.xib b/Tusker/Views/Hashtag Cell/HashtagTableViewCell.xib index a38016d4..4f512708 100644 --- a/Tusker/Views/Hashtag Cell/HashtagTableViewCell.xib +++ b/Tusker/Views/Hashtag Cell/HashtagTableViewCell.xib @@ -1,8 +1,9 @@ - + - + + @@ -17,8 +18,8 @@ diff --git a/Tusker/Views/Hashtag Cell/TrendingHashtagTableViewCell.swift b/Tusker/Views/Hashtag Cell/TrendingHashtagTableViewCell.swift new file mode 100644 index 00000000..60403543 --- /dev/null +++ b/Tusker/Views/Hashtag Cell/TrendingHashtagTableViewCell.swift @@ -0,0 +1,41 @@ +// +// TrendingHashtagTableViewCell.swift +// Tusker +// +// Created by Shadowfacts on 1/24/21. +// Copyright © 2021 Shadowfacts. All rights reserved. +// + +import UIKit +import Pachyderm + +class TrendingHashtagTableViewCell: UITableViewCell { + + @IBOutlet weak var hashtagLabel: UILabel! + @IBOutlet weak var peopleTodayLabel: UILabel! + @IBOutlet weak var historyView: HashtagHistoryView! + + override func awakeFromNib() { + super.awakeFromNib() + } + + func updateUI(hashtag: Hashtag) { + hashtagLabel.text = "#\(hashtag.name)" + historyView.setHistory(hashtag.history) + historyView.isHidden = hashtag.history == nil || hashtag.history!.count < 2 + + if let history = hashtag.history { + let sorted = history.sorted(by: { $0.day < $1.day }) + let lastTwo = sorted[(sorted.count - 2)...] + let accounts = lastTwo.map(\.accounts).reduce(0, +) + let uses = lastTwo.map(\.uses).reduce(0, +) + + let format = NSLocalizedString("trending hashtag info", comment: "trending hashtag posts and people") + peopleTodayLabel.text = String.localizedStringWithFormat(format, accounts, uses) + peopleTodayLabel.isHidden = false + } else { + peopleTodayLabel.isHidden = true + } + } + +} diff --git a/Tusker/Views/Hashtag Cell/TrendingHashtagTableViewCell.xib b/Tusker/Views/Hashtag Cell/TrendingHashtagTableViewCell.xib new file mode 100644 index 00000000..1b2b6d78 --- /dev/null +++ b/Tusker/Views/Hashtag Cell/TrendingHashtagTableViewCell.xib @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tusker/en.lproj/Localizable.stringsdict b/Tusker/en.lproj/Localizable.stringsdict new file mode 100644 index 00000000..febdd157 --- /dev/null +++ b/Tusker/en.lproj/Localizable.stringsdict @@ -0,0 +1,33 @@ + + + + + trending hashtag info + + NSStringLocalizedFormatKey + %#@accounts@, %#@posts@ recently + posts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + 1 post + other + %u posts + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + 1 person + other + %u people + + + +