Compare commits

..

6 Commits

33 changed files with 181 additions and 44 deletions

View File

@ -1,5 +1,11 @@
# Changelog # Changelog
## 2023.5 (84)
Bugfixes:
- Fix notifications scrolling to top when refreshing
- Fix decoding statuses failing on GoToSocial
- Fix assorted issues when collapsing/expanding between sidebar and tab bar modes
## 2023.5 (83) ## 2023.5 (83)
This build contains significant refactors to the notifications screen, please report any issues you encounter. This build contains significant refactors to the notifications screen, please report any issues you encounter.

View File

@ -120,8 +120,8 @@ class PollController: ViewController {
} }
.padding(8) .padding(8)
.background( .background(
backgroundColor RoundedRectangle(cornerRadius: 10, style: .continuous)
.cornerRadius(10) .foregroundColor(backgroundColor)
) )
.onChange(of: controller.duration) { newValue in .onChange(of: controller.duration) { newValue in
poll.duration = newValue.timeInterval poll.duration = newValue.timeInterval

View File

@ -1,13 +1,14 @@
// //
// CharacterCounterTests.swift // CharacterCounterTests.swift
// PachydermTests // ComposeUITests
// //
// Created by Shadowfacts on 9/29/18. // Created by Shadowfacts on 9/29/18.
// Copyright © 2018 Shadowfacts. All rights reserved. // Copyright © 2018 Shadowfacts. All rights reserved.
// //
import XCTest import XCTest
@testable import Pachyderm @testable import ComposeUI
import InstanceFeatures
class CharacterCounterTests: XCTestCase { class CharacterCounterTests: XCTestCase {
@ -16,32 +17,34 @@ class CharacterCounterTests: XCTestCase {
override func tearDown() { override func tearDown() {
} }
let features = InstanceFeatures()
func testCountEmpty() { func testCountEmpty() {
XCTAssertEqual(CharacterCounter.count(text: ""), 0) XCTAssertEqual(CharacterCounter.count(text: "", for: features), 0)
} }
func testCountPlainText() { func testCountPlainText() {
XCTAssertEqual(CharacterCounter.count(text: "This is an example message"), 26) XCTAssertEqual(CharacterCounter.count(text: "This is an example message", for: features), 26)
XCTAssertEqual(CharacterCounter.count(text: "This is an example message with an Emoji: 😄"), 43) XCTAssertEqual(CharacterCounter.count(text: "This is an example message with an Emoji: 😄", for: features), 43)
XCTAssertEqual(CharacterCounter.count(text: "😄😄😄😄😄😄😄"), 7) XCTAssertEqual(CharacterCounter.count(text: "😄😄😄😄😄😄😄", for: features), 7)
} }
func testCountLinks() { func testCountLinks() {
XCTAssertEqual(CharacterCounter.count(text: "This is an example with a link: https://example.com"), 55) XCTAssertEqual(CharacterCounter.count(text: "This is an example with a link: https://example.com", for: features), 55)
XCTAssertEqual(CharacterCounter.count(text: "This is an example with a link 😄: https://example.com"), 57) XCTAssertEqual(CharacterCounter.count(text: "This is an example with a link 😄: https://example.com", for: features), 57)
XCTAssertEqual(CharacterCounter.count(text: "😄😄😄😄😄😄😄: https://example.com"), 32) XCTAssertEqual(CharacterCounter.count(text: "😄😄😄😄😄😄😄: https://example.com", for: features), 32)
XCTAssertEqual(CharacterCounter.count(text: "This is an example with a link: https://a.much.longer.example.com/link?foo=bar#baz"), 55) XCTAssertEqual(CharacterCounter.count(text: "This is an example with a link: https://a.much.longer.example.com/link?foo=bar#baz", for: features), 55)
} }
func testCountLocalMentions() { func testCountLocalMentions() {
XCTAssertEqual(CharacterCounter.count(text: "hello @example"), 14) XCTAssertEqual(CharacterCounter.count(text: "hello @example", for: features), 14)
XCTAssertEqual(CharacterCounter.count(text: "@some_really_long_name"), 22) XCTAssertEqual(CharacterCounter.count(text: "@some_really_long_name", for: features), 22)
} }
func testCountRemoteMentions() { func testCountRemoteMentions() {
XCTAssertEqual(CharacterCounter.count(text: "hello @example@some.remote.social"), 14) XCTAssertEqual(CharacterCounter.count(text: "hello @example@some.remote.social", for: features), 14)
XCTAssertEqual(CharacterCounter.count(text: "hello @some_really_long_name@some-long.remote-instance.social"), 28) XCTAssertEqual(CharacterCounter.count(text: "hello @some_really_long_name@some-long.remote-instance.social", for: features), 28)
} }
} }

View File

@ -155,6 +155,7 @@ public class DuckableContainerViewController: UIViewController, DuckableViewCont
bottomConstraint.isActive = true bottomConstraint.isActive = true
child.view.layer.cornerRadius = duckedCornerRadius child.view.layer.cornerRadius = duckedCornerRadius
child.view.layer.cornerCurve = .continuous
child.view.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] child.view.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
child.view.layer.masksToBounds = true child.view.layer.masksToBounds = true
} }

View File

@ -17,7 +17,7 @@
BlueprintIdentifier = "D61099AA2144B0CC00432DC2" BlueprintIdentifier = "D61099AA2144B0CC00432DC2"
BuildableName = "Pachyderm.framework" BuildableName = "Pachyderm.framework"
BlueprintName = "Pachyderm" BlueprintName = "Pachyderm"
ReferencedContainer = "container:Tusker.xcodeproj"> ReferencedContainer = "container:../../Tusker.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </BuildActionEntry>
</BuildActionEntries> </BuildActionEntries>
@ -35,7 +35,7 @@
BlueprintIdentifier = "PachydermTests" BlueprintIdentifier = "PachydermTests"
BuildableName = "PachydermTests" BuildableName = "PachydermTests"
BlueprintName = "PachydermTests" BlueprintName = "PachydermTests"
ReferencedContainer = "container:Pachyderm"> ReferencedContainer = "container:">
</BuildableReference> </BuildableReference>
</TestableReference> </TestableReference>
</Testables> </Testables>
@ -56,7 +56,7 @@
BlueprintIdentifier = "D61099AA2144B0CC00432DC2" BlueprintIdentifier = "D61099AA2144B0CC00432DC2"
BuildableName = "Pachyderm.framework" BuildableName = "Pachyderm.framework"
BlueprintName = "Pachyderm" BlueprintName = "Pachyderm"
ReferencedContainer = "container:Tusker.xcodeproj"> ReferencedContainer = "container:../../Tusker.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
</LaunchAction> </LaunchAction>
@ -72,7 +72,7 @@
BlueprintIdentifier = "D61099AA2144B0CC00432DC2" BlueprintIdentifier = "D61099AA2144B0CC00432DC2"
BuildableName = "Pachyderm.framework" BuildableName = "Pachyderm.framework"
BlueprintName = "Pachyderm" BlueprintName = "Pachyderm"
ReferencedContainer = "container:Tusker.xcodeproj"> ReferencedContainer = "container:../../Tusker.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
</ProfileAction> </ProfileAction>

View File

@ -51,8 +51,12 @@ public final class Status: StatusProtocol, Decodable, Sendable {
do { do {
self.url = try container.decodeIfPresent(WebURL.self, forKey: .url) self.url = try container.decodeIfPresent(WebURL.self, forKey: .url)
} catch { } catch {
let s = (try? container.decode(String.self, forKey: .url)) ?? "<failed to decode string>" let s = try? container.decode(String.self, forKey: .url)
throw DecodingError.dataCorruptedError(forKey: .url, in: container, debugDescription: "Could not decode URL '\(s)', reason: \(String(describing: error))") if s == "" {
self.url = nil
} else {
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath + [CodingKeys.url], debugDescription: "Could not decode URL '\(s ?? "<failed to decode string>")'", underlyingError: error))
}
} }
self.account = try container.decode(Account.self, forKey: .account) self.account = try container.decode(Account.self, forKey: .account)
self.inReplyToID = try container.decodeIfPresent(String.self, forKey: .inReplyToID) self.inReplyToID = try container.decodeIfPresent(String.self, forKey: .inReplyToID)

View File

@ -8,6 +8,7 @@
import XCTest import XCTest
@testable import Pachyderm @testable import Pachyderm
@MainActor
class NotificationGroupTests: XCTestCase { class NotificationGroupTests: XCTestCase {
let decoder: JSONDecoder = { let decoder: JSONDecoder = {

View File

@ -0,0 +1,53 @@
//
// StatusTests.swift
//
//
// Created by Shadowfacts on 5/8/23.
//
import XCTest
@testable import Pachyderm
final class StatusTests: XCTestCase {
func testDecode() {
let data = """
{
"id": "1",
"uri": "https://example.com/a/1",
"url": "",
"account": {
"id": "2",
"username": "a",
"acct": "a",
"display_name": "",
"locked": false,
"created_at": 0,
"followers_count": 0,
"following_count": 0,
"statuses_count": 0,
"note": "",
"url": "https://example.com/a"
},
"content": "",
"created_at": 0,
"emojis": [],
"reblogs_count": 0,
"favourites_count": 0,
"sensitive": false,
"spoiler_text": "",
"visibility": "public",
"media_attachments": [],
"mentions": [],
"tags": []
}
""".data(using: .utf8)!
do {
_ = try JSONDecoder().decode(Status.self, from: data)
} catch {
print(error)
XCTFail()
}
}
}

View File

@ -27,7 +27,7 @@ public struct AvatarImageView: View {
imageView imageView
.resizable() .resizable()
.frame(width: size, height: size) .frame(width: size, height: size)
.cornerRadius(style.cornerRadiusFraction * size) .clipShape(RoundedRectangle(cornerRadius: style.cornerRadiusFraction * size, style: .continuous))
.task { @MainActor in .task { @MainActor in
image = nil image = nil
if let url { if let url {

View File

@ -2342,7 +2342,7 @@
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements; CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83; CURRENT_PROJECT_VERSION = 84;
INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_FILE = Tusker/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@ -2408,7 +2408,7 @@
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements; CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83; CURRENT_PROJECT_VERSION = 84;
INFOPLIST_FILE = OpenInTusker/Info.plist; INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3; "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
@ -2434,7 +2434,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83; CURRENT_PROJECT_VERSION = 84;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
@ -2463,7 +2463,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83; CURRENT_PROJECT_VERSION = 84;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
@ -2492,7 +2492,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83; CURRENT_PROJECT_VERSION = 84;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
@ -2647,7 +2647,7 @@
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements; CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83; CURRENT_PROJECT_VERSION = 84;
INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_FILE = Tusker/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@ -2678,7 +2678,7 @@
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements; CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83; CURRENT_PROJECT_VERSION = 84;
INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_FILE = Tusker/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@ -2784,7 +2784,7 @@
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements; CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83; CURRENT_PROJECT_VERSION = 84;
INFOPLIST_FILE = OpenInTusker/Info.plist; INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3; "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
@ -2810,7 +2810,7 @@
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements; CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83; CURRENT_PROJECT_VERSION = 84;
INFOPLIST_FILE = OpenInTusker/Info.plist; INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3; "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;

View File

@ -27,7 +27,9 @@ class FeaturedProfileCollectionViewCell: UICollectionViewCell {
super.awakeFromNib() super.awakeFromNib()
avatarContainerView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarContainerView) avatarContainerView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarContainerView)
avatarContainerView.layer.cornerCurve = .continuous
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView) avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
avatarImageView.layer.cornerCurve = .continuous
displayNameLabel.font = UIFontMetrics(forTextStyle: .title1).scaledFont(for: .systemFont(ofSize: 20, weight: .semibold)) displayNameLabel.font = UIFontMetrics(forTextStyle: .title1).scaledFont(for: .systemFont(ofSize: 20, weight: .semibold))
displayNameLabel.adjustsFontForContentSizeCategory = true displayNameLabel.adjustsFontForContentSizeCategory = true
@ -41,6 +43,7 @@ class FeaturedProfileCollectionViewCell: UICollectionViewCell {
backgroundColor = .clear backgroundColor = .clear
clippingView.backgroundColor = .appBackground clippingView.backgroundColor = .appBackground
clippingView.layer.cornerRadius = 5 clippingView.layer.cornerRadius = 5
clippingView.layer.cornerCurve = .continuous
clippingView.layer.borderWidth = 1 clippingView.layer.borderWidth = 1
clippingView.layer.masksToBounds = true clippingView.layer.masksToBounds = true
layer.shadowOpacity = 0.2 layer.shadowOpacity = 0.2

View File

@ -36,14 +36,17 @@ class SuggestedProfileCardCollectionViewCell: UICollectionViewCell {
layer.shadowOffset = .zero layer.shadowOffset = .zero
layer.masksToBounds = false layer.masksToBounds = false
contentView.layer.cornerRadius = 12.5 contentView.layer.cornerRadius = 12.5
contentView.layer.cornerCurve = .continuous
contentView.backgroundColor = .appGroupedCellBackground contentView.backgroundColor = .appGroupedCellBackground
updateLayerColors() updateLayerColors()
headerImageView.cache = .headers headerImageView.cache = .headers
avatarContainerView.layer.masksToBounds = true avatarContainerView.layer.masksToBounds = true
avatarContainerView.layer.cornerCurve = .continuous
avatarImageView.cache = .avatars avatarImageView.cache = .avatars
avatarImageView.layer.masksToBounds = true avatarImageView.layer.masksToBounds = true
avatarImageView.layer.cornerCurve = .continuous
displayNameLabel.font = UIFontMetrics(forTextStyle: .title1).scaledFont(for: .systemFont(ofSize: 24, weight: .semibold)) displayNameLabel.font = UIFontMetrics(forTextStyle: .title1).scaledFont(for: .systemFont(ofSize: 24, weight: .semibold))
displayNameLabel.adjustsFontForContentSizeCategory = true displayNameLabel.adjustsFontForContentSizeCategory = true

View File

@ -57,6 +57,7 @@ class TrendingLinkCardCollectionViewCell: UICollectionViewCell {
layer.shadowOffset = .zero layer.shadowOffset = .zero
layer.masksToBounds = false layer.masksToBounds = false
contentView.layer.cornerRadius = 12.5 contentView.layer.cornerRadius = 12.5
contentView.layer.cornerCurve = .continuous
contentView.backgroundColor = .appGroupedCellBackground contentView.backgroundColor = .appGroupedCellBackground
updateLayerColors() updateLayerColors()

View File

@ -86,7 +86,9 @@ class FastSwitchingAccountView: UIView {
avatarImageView.translatesAutoresizingMaskIntoConstraints = false avatarImageView.translatesAutoresizingMaskIntoConstraints = false
avatarImageView.layer.masksToBounds = true avatarImageView.layer.masksToBounds = true
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 40 avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 40
avatarImageView.layer.cornerCurve = .continuous
avatarImageView.image = UIImage(systemName: Preferences.shared.avatarStyle == .circle ? "person.crop.circle" : "person.crop.square") avatarImageView.image = UIImage(systemName: Preferences.shared.avatarStyle == .circle ? "person.crop.circle" : "person.crop.square")
// todo: this should really be fit, but somehow the image view ends up being 37pt tall?
avatarImageView.contentMode = .scaleAspectFill avatarImageView.contentMode = .scaleAspectFill
addSubview(avatarImageView) addSubview(avatarImageView)

View File

@ -225,6 +225,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
// so that explore items aren't shown multiple times. // so that explore items aren't shown multiple times.
let exploreNav = tabBarViewController.viewController(for: .explore) as! UINavigationController let exploreNav = tabBarViewController.viewController(for: .explore) as! UINavigationController
// make sure there's a root ExploreViewController
let explore: ExploreViewController let explore: ExploreViewController
if let existing = exploreNav.viewControllers.first as? ExploreViewController { if let existing = exploreNav.viewControllers.first as? ExploreViewController {
explore = existing explore = existing
@ -238,14 +239,23 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
explore.loadViewIfNeeded() explore.loadViewIfNeeded()
let search = secondaryNavController.viewControllers.first as! InlineTrendsViewController let search = secondaryNavController.viewControllers.first as! InlineTrendsViewController
// Copy the search query from the search VC to the Explore VC's search controller. if search.searchController.isActive {
let query = search.searchController.searchBar.text ?? "" // Copy the search query from the search VC to the Explore VC's search controller.
explore.searchController.searchBar.text = query let query = search.searchController.searchBar.text ?? ""
// Instruct the explore controller to show its search controller immediately upon its first appearance. explore.searchController.searchBar.text = query
// explore.searchController.isActive can't be set directly, see FB7814561 // Instruct the explore controller to show its search controller immediately upon its first appearance.
explore.searchControllerStatusOnAppearance = !query.isEmpty // explore.searchController.isActive can't be set directly, see FB7814561
// Copy the results from the search VC's results controller to avoid the delay introduced by an extra network request explore.searchControllerStatusOnAppearance = !query.isEmpty
explore.resultsController.loadResults(from: search.resultsController) // Copy the results from the search VC's results controller to avoid the delay introduced by an extra network request
explore.resultsController.loadResults(from: search.resultsController)
} else {
// if there is more than just the InlineTrendsVC, and the search VC is not active,
// then the user selected something from the trends screen
if secondaryNavController.viewControllers.count >= 2 {
// make sure there's a corresponding trends VC in the collapsed nav that they can go back to
exploreNav.pushViewController(TrendsViewController(mastodonController: mastodonController), animated: false)
}
}
// Transfer the navigation stack, dropping the search VC, to keep anything the user has opened // Transfer the navigation stack, dropping the search VC, to keep anything the user has opened
transferNavigationStack(from: .explore, to: exploreNav, dropFirst: true, append: true) transferNavigationStack(from: .explore, to: exploreNav, dropFirst: true, append: true)
@ -329,14 +339,16 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
exploreItem = .favorites exploreItem = .favorites
case let listVC as ListTimelineViewController: case let listVC as ListTimelineViewController:
exploreItem = .list(listVC.list) exploreItem = .list(listVC.list)
case let hashtagVC as HashtagTimelineViewController: case let hashtagVC as HashtagTimelineViewController where hashtagVC.isHashtagSaved:
exploreItem = .savedHashtag(hashtagVC.hashtag) exploreItem = .savedHashtag(hashtagVC.hashtag)
case let instanceVC as InstanceTimelineViewController: case let instanceVC as InstanceTimelineViewController:
exploreItem = .savedInstance(instanceVC.instanceURL) exploreItem = .savedInstance(instanceVC.instanceURL)
case is TrendingStatusesViewController, is TrendingHashtagsViewController, is TrendingLinksViewController: case is TrendsViewController:
exploreItem = .explore exploreItem = .explore
// these three VCs are part of the root SearchViewController, so we don't need to transfer them // skip transferring the ExploreViewController and TrendsViewController
skipFirst = 2 skipFirst = 2
// prepend the InlineTrendsViewController
toPrepend = getOrCreateNavigationStack(item: .explore).first!
default: default:
// transfer the navigation stack prepending, the existing explore VC // transfer the navigation stack prepending, the existing explore VC
// if there was other stuff on the explore stack, it will get discarded // if there was other stuff on the explore stack, it will get discarded

View File

@ -149,6 +149,7 @@ class ActionNotificationGroupCollectionViewCell: UICollectionViewListCell {
imageView.contentMode = .scaleAspectFit imageView.contentMode = .scaleAspectFit
imageView.layer.masksToBounds = true imageView.layer.masksToBounds = true
imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30 imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30
imageView.layer.cornerCurve = .continuous
avatarStack.addArrangedSubview(imageView) avatarStack.addArrangedSubview(imageView)
imageView.update(for: avatarURL) imageView.update(for: avatarURL)
} }

View File

@ -123,6 +123,7 @@ class FollowNotificationGroupCollectionViewCell: UICollectionViewListCell {
let imageView = CachedImageView(cache: .avatars) let imageView = CachedImageView(cache: .avatars)
imageView.layer.masksToBounds = true imageView.layer.masksToBounds = true
imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30 imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30
imageView.layer.cornerCurve = .continuous
imageView.update(for: avatarURL) imageView.update(for: avatarURL)
avatarStack.addArrangedSubview(imageView) avatarStack.addArrangedSubview(imageView)
} }

View File

@ -21,6 +21,7 @@ class FollowRequestNotificationCollectionViewCell: UICollectionViewListCell {
private let avatarImageView = CachedImageView(cache: .avatars).configure { private let avatarImageView = CachedImageView(cache: .avatars).configure {
$0.layer.masksToBounds = true $0.layer.masksToBounds = true
$0.layer.cornerCurve = .continuous
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
$0.widthAnchor.constraint(equalTo: $0.heightAnchor), $0.widthAnchor.constraint(equalTo: $0.heightAnchor),
]) ])

View File

@ -355,8 +355,24 @@ extension NotificationsCollectionViewController {
} }
func handlePrependItems(_ timelineItems: [NotificationGroup]) async { func handlePrependItems(_ timelineItems: [NotificationGroup]) async {
let topItem = dataSource.snapshot().itemIdentifiers(inSection: .notifications).first
// we always replace all, because new items are merged with existing ones // we always replace all, because new items are merged with existing ones
await handleReplaceAllItems(timelineItems) await handleReplaceAllItems(timelineItems)
// preserve the scroll position
// todo: this won't work for cmd+r when not at top
if let topID = topItem?.group?.notifications.first?.id {
// the exact item may have changed, due to merging
let newTopGroup = timelineItems.first {
$0.notifications.contains {
$0.id == topID
}
}!
if let newTopIndexPath = dataSource.indexPath(for: .group(newTopGroup)) {
collectionView.scrollToItem(at: newTopIndexPath, at: .top, animated: false)
}
}
} }
func loadOlder() async throws -> [NotificationGroup] { func loadOlder() async throws -> [NotificationGroup] {

View File

@ -15,7 +15,7 @@ class HashtagTimelineViewController: TimelineViewController {
var toggleSaveButton: UIBarButtonItem! var toggleSaveButton: UIBarButtonItem!
private var isHashtagSaved: Bool { var isHashtagSaved: Bool {
let req = SavedHashtag.fetchRequest(name: hashtag.name, account: mastodonController.accountInfo!) let req = SavedHashtag.fetchRequest(name: hashtag.name, account: mastodonController.accountInfo!)
return mastodonController.persistentContainer.viewContext.objectExists(for: req) return mastodonController.persistentContainer.viewContext.objectExists(for: req)
} }

View File

@ -153,6 +153,7 @@ extension TimelineLikeCollectionViewController {
} }
await apply(snapshot, animatingDifferences: false) await apply(snapshot, animatingDifferences: false)
// todo: this won't work for cmd+r when not at top
if let first, if let first,
let indexPath = dataSource.indexPath(for: first) { let indexPath = dataSource.indexPath(for: first) {
// TODO: i can't tell if this actually works or not // TODO: i can't tell if this actually works or not

View File

@ -22,6 +22,7 @@ class AccountCollectionViewCell: UICollectionViewListCell {
private let avatarImageView = CachedImageView(cache: .avatars).configure { private let avatarImageView = CachedImageView(cache: .avatars).configure {
$0.layer.masksToBounds = true $0.layer.masksToBounds = true
$0.layer.cornerCurve = .continuous
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
$0.widthAnchor.constraint(equalToConstant: 50), $0.widthAnchor.constraint(equalToConstant: 50),
$0.heightAnchor.constraint(equalToConstant: 50), $0.heightAnchor.constraint(equalToConstant: 50),

View File

@ -28,6 +28,7 @@ class AccountTableViewCell: UITableViewCell {
super.awakeFromNib() super.awakeFromNib()
avatarImageView.layer.masksToBounds = true avatarImageView.layer.masksToBounds = true
avatarImageView.layer.cornerCurve = .continuous
usernameLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .light)) usernameLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .light))
usernameLabel.adjustsFontForContentSizeCategory = true usernameLabel.adjustsFontForContentSizeCategory = true

View File

@ -22,6 +22,7 @@ class LargeAccountDetailView: UIView {
avatarImageView.translatesAutoresizingMaskIntoConstraints = false avatarImageView.translatesAutoresizingMaskIntoConstraints = false
avatarImageView.layer.masksToBounds = true avatarImageView.layer.masksToBounds = true
avatarImageView.layer.cornerCurve = .continuous
addSubview(avatarImageView) addSubview(avatarImageView)
displayNameLabel.translatesAutoresizingMaskIntoConstraints = false displayNameLabel.translatesAutoresizingMaskIntoConstraints = false

View File

@ -368,11 +368,13 @@ class AttachmentView: GIFImageView {
let first = stack.arrangedSubviews.first! let first = stack.arrangedSubviews.first!
first.layer.masksToBounds = true first.layer.masksToBounds = true
first.layer.cornerRadius = 4 first.layer.cornerRadius = 4
first.layer.cornerCurve = .continuous
if stack.arrangedSubviews.count > 1 { if stack.arrangedSubviews.count > 1 {
first.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner] first.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
let last = stack.arrangedSubviews.last! let last = stack.arrangedSubviews.last!
last.layer.masksToBounds = true last.layer.masksToBounds = true
last.layer.cornerRadius = 4 last.layer.cornerRadius = 4
last.layer.cornerCurve = .continuous
last.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner] last.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
} }

View File

@ -85,6 +85,7 @@ class AttachmentsContainerView: UIView {
case 1: case 1:
let attachmentView = createAttachmentView(index: 0, hSize: .full, vSize: .full) let attachmentView = createAttachmentView(index: 0, hSize: .full, vSize: .full)
attachmentView.layer.cornerRadius = 5 attachmentView.layer.cornerRadius = 5
attachmentView.layer.cornerCurve = .continuous
attachmentView.layer.masksToBounds = true attachmentView.layer.masksToBounds = true
fillView(attachmentView) fillView(attachmentView)
sendSubviewToBack(attachmentView) sendSubviewToBack(attachmentView)
@ -95,10 +96,12 @@ class AttachmentsContainerView: UIView {
case 2: case 2:
let left = createAttachmentView(index: 0, hSize: .half, vSize: .full) let left = createAttachmentView(index: 0, hSize: .half, vSize: .full)
left.layer.cornerRadius = 5 left.layer.cornerRadius = 5
left.layer.cornerCurve = .continuous
left.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner] left.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
left.layer.masksToBounds = true left.layer.masksToBounds = true
let right = createAttachmentView(index: 1, hSize: .half, vSize: .full) let right = createAttachmentView(index: 1, hSize: .half, vSize: .full)
right.layer.cornerRadius = 5 right.layer.cornerRadius = 5
right.layer.cornerCurve = .continuous
right.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner] right.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
right.layer.masksToBounds = true right.layer.masksToBounds = true
let stack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [ let stack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
@ -116,14 +119,17 @@ class AttachmentsContainerView: UIView {
case 3: case 3:
let left = createAttachmentView(index: 0, hSize: .half, vSize: .full) let left = createAttachmentView(index: 0, hSize: .half, vSize: .full)
left.layer.cornerRadius = 5 left.layer.cornerRadius = 5
left.layer.cornerCurve = .continuous
left.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner] left.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
left.layer.masksToBounds = true left.layer.masksToBounds = true
let topRight = createAttachmentView(index: 1, hSize: .half, vSize: .half) let topRight = createAttachmentView(index: 1, hSize: .half, vSize: .half)
topRight.layer.cornerRadius = 5 topRight.layer.cornerRadius = 5
topRight.layer.cornerCurve = .continuous
topRight.layer.maskedCorners = .layerMaxXMinYCorner topRight.layer.maskedCorners = .layerMaxXMinYCorner
topRight.layer.masksToBounds = true topRight.layer.masksToBounds = true
let bottomRight = createAttachmentView(index: 2, hSize: .half, vSize: .half) let bottomRight = createAttachmentView(index: 2, hSize: .half, vSize: .half)
bottomRight.layer.cornerRadius = 5 bottomRight.layer.cornerRadius = 5
bottomRight.layer.cornerCurve = .continuous
bottomRight.layer.maskedCorners = .layerMaxXMaxYCorner bottomRight.layer.maskedCorners = .layerMaxXMaxYCorner
bottomRight.layer.masksToBounds = true bottomRight.layer.masksToBounds = true
let innerStack = createAttachmentsStack(axis: .vertical, arrangedSubviews: [ let innerStack = createAttachmentsStack(axis: .vertical, arrangedSubviews: [
@ -148,10 +154,12 @@ class AttachmentsContainerView: UIView {
case 4: case 4:
let topLeft = createAttachmentView(index: 0, hSize: .half, vSize: .half) let topLeft = createAttachmentView(index: 0, hSize: .half, vSize: .half)
topLeft.layer.cornerRadius = 5 topLeft.layer.cornerRadius = 5
topLeft.layer.cornerCurve = .continuous
topLeft.layer.maskedCorners = .layerMinXMinYCorner topLeft.layer.maskedCorners = .layerMinXMinYCorner
topLeft.layer.masksToBounds = true topLeft.layer.masksToBounds = true
let bottomLeft = createAttachmentView(index: 2, hSize: .half, vSize: .half) let bottomLeft = createAttachmentView(index: 2, hSize: .half, vSize: .half)
bottomLeft.layer.cornerRadius = 5 bottomLeft.layer.cornerRadius = 5
bottomLeft.layer.cornerCurve = .continuous
bottomLeft.layer.maskedCorners = .layerMinXMaxYCorner bottomLeft.layer.maskedCorners = .layerMinXMaxYCorner
bottomLeft.layer.masksToBounds = true bottomLeft.layer.masksToBounds = true
let left = createAttachmentsStack(axis: .vertical, arrangedSubviews: [ let left = createAttachmentsStack(axis: .vertical, arrangedSubviews: [
@ -161,10 +169,12 @@ class AttachmentsContainerView: UIView {
attachmentStacks.add(left) attachmentStacks.add(left)
let topRight = createAttachmentView(index: 1, hSize: .half, vSize: .half) let topRight = createAttachmentView(index: 1, hSize: .half, vSize: .half)
topRight.layer.cornerRadius = 5 topRight.layer.cornerRadius = 5
topRight.layer.cornerCurve = .continuous
topRight.layer.maskedCorners = .layerMaxXMinYCorner topRight.layer.maskedCorners = .layerMaxXMinYCorner
topRight.layer.masksToBounds = true topRight.layer.masksToBounds = true
let bottomRight = createAttachmentView(index: 3, hSize: .half, vSize: .half) let bottomRight = createAttachmentView(index: 3, hSize: .half, vSize: .half)
bottomRight.layer.cornerRadius = 5 bottomRight.layer.cornerRadius = 5
bottomRight.layer.cornerCurve = .continuous
bottomRight.layer.maskedCorners = .layerMaxXMaxYCorner bottomRight.layer.maskedCorners = .layerMaxXMaxYCorner
bottomRight.layer.masksToBounds = true bottomRight.layer.masksToBounds = true
let right = createAttachmentsStack(axis: .vertical, arrangedSubviews: [ let right = createAttachmentsStack(axis: .vertical, arrangedSubviews: [
@ -196,6 +206,7 @@ class AttachmentsContainerView: UIView {
moreView.isUserInteractionEnabled = true moreView.isUserInteractionEnabled = true
moreView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(moreViewTapped))) moreView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(moreViewTapped)))
moreView.layer.cornerRadius = 5 moreView.layer.cornerRadius = 5
moreView.layer.cornerCurve = .continuous
moreView.layer.maskedCorners = .layerMaxXMaxYCorner moreView.layer.maskedCorners = .layerMaxXMaxYCorner
moreView.layer.masksToBounds = true moreView.layer.masksToBounds = true
let moreLabel = UILabel() let moreLabel = UILabel()
@ -208,6 +219,7 @@ class AttachmentsContainerView: UIView {
let topLeft = createAttachmentView(index: 0, hSize: .half, vSize: .half) let topLeft = createAttachmentView(index: 0, hSize: .half, vSize: .half)
topLeft.layer.cornerRadius = 5 topLeft.layer.cornerRadius = 5
topLeft.layer.cornerCurve = .continuous
topLeft.layer.maskedCorners = .layerMinXMinYCorner topLeft.layer.maskedCorners = .layerMinXMinYCorner
topLeft.layer.masksToBounds = true topLeft.layer.masksToBounds = true
let bottomLeft = createAttachmentView(index: 2, hSize: .half, vSize: .half) let bottomLeft = createAttachmentView(index: 2, hSize: .half, vSize: .half)
@ -221,6 +233,7 @@ class AttachmentsContainerView: UIView {
attachmentStacks.add(left) attachmentStacks.add(left)
let topRight = createAttachmentView(index: 1, hSize: .half, vSize: .half) let topRight = createAttachmentView(index: 1, hSize: .half, vSize: .half)
topRight.layer.cornerRadius = 5 topRight.layer.cornerRadius = 5
topRight.layer.cornerCurve = .continuous
topRight.layer.maskedCorners = .layerMaxXMinYCorner topRight.layer.maskedCorners = .layerMaxXMinYCorner
topRight.layer.masksToBounds = true topRight.layer.masksToBounds = true
let right = createAttachmentsStack(axis: .vertical, arrangedSubviews: [ let right = createAttachmentsStack(axis: .vertical, arrangedSubviews: [

View File

@ -35,6 +35,7 @@ class PollOptionCheckboxView: UIView {
let size: CGFloat = 20 let size: CGFloat = 20
layer.cornerRadius = (multiple ? 0.1 : 0.5) * size layer.cornerRadius = (multiple ? 0.1 : 0.5) * size
layer.cornerCurve = .continuous
layer.borderWidth = 2 layer.borderWidth = 2
imageView.translatesAutoresizingMaskIntoConstraints = false imageView.translatesAutoresizingMaskIntoConstraints = false

View File

@ -22,6 +22,7 @@ class PollOptionView: UIView {
let minHeight: CGFloat = 35 let minHeight: CGFloat = 35
layer.cornerRadius = 0.1 * minHeight layer.cornerRadius = 0.1 * minHeight
layer.cornerCurve = .continuous
backgroundColor = unselectedBackgroundColor backgroundColor = unselectedBackgroundColor
checkbox.translatesAutoresizingMaskIntoConstraints = false checkbox.translatesAutoresizingMaskIntoConstraints = false
@ -66,6 +67,7 @@ class PollOptionView: UIView {
fillView.backgroundColor = .tintColor.withAlphaComponent(0.6) fillView.backgroundColor = .tintColor.withAlphaComponent(0.6)
fillView.layer.zPosition = -1 fillView.layer.zPosition = -1
fillView.layer.cornerRadius = layer.cornerRadius fillView.layer.cornerRadius = layer.cornerRadius
fillView.layer.cornerCurve = .continuous
addSubview(fillView) addSubview(fillView)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([

View File

@ -42,6 +42,7 @@ class ProfileHeaderMovedOverlayView: UIView {
avatarImageView = CachedImageView(cache: .avatars) avatarImageView = CachedImageView(cache: .avatars)
avatarImageView.layer.masksToBounds = true avatarImageView.layer.masksToBounds = true
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 50 avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 50
avatarImageView.layer.cornerCurve = .continuous
avatarImageView.addInteraction(UIPointerInteraction(delegate: self)) avatarImageView.addInteraction(UIPointerInteraction(delegate: self))
avatarImageView.isUserInteractionEnabled = true avatarImageView.isUserInteractionEnabled = true

View File

@ -66,7 +66,9 @@ class ProfileHeaderView: UIView {
avatarContainerView.backgroundColor = .appBackground avatarContainerView.backgroundColor = .appBackground
avatarContainerView.layer.masksToBounds = true avatarContainerView.layer.masksToBounds = true
avatarContainerView.layer.cornerCurve = .continuous
avatarImageView.layer.masksToBounds = true avatarImageView.layer.masksToBounds = true
avatarImageView.layer.cornerCurve = .continuous
avatarImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(avatarPressed))) avatarImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(avatarPressed)))
avatarImageView.isUserInteractionEnabled = true avatarImageView.isUserInteractionEnabled = true

View File

@ -29,6 +29,7 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
private static let avatarImageViewSize: CGFloat = 50 private static let avatarImageViewSize: CGFloat = 50
private(set) lazy var avatarImageView = CachedImageView(cache: .avatars).configure { private(set) lazy var avatarImageView = CachedImageView(cache: .avatars).configure {
$0.layer.masksToBounds = true $0.layer.masksToBounds = true
$0.layer.cornerCurve = .continuous
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
$0.heightAnchor.constraint(equalToConstant: ConversationMainStatusCollectionViewCell.avatarImageViewSize), $0.heightAnchor.constraint(equalToConstant: ConversationMainStatusCollectionViewCell.avatarImageViewSize),
$0.widthAnchor.constraint(equalToConstant: ConversationMainStatusCollectionViewCell.avatarImageViewSize), $0.widthAnchor.constraint(equalToConstant: ConversationMainStatusCollectionViewCell.avatarImageViewSize),

View File

@ -102,6 +102,7 @@ class StatusCardView: UIView {
hStack.spacing = 4 hStack.spacing = 4
hStack.clipsToBounds = true hStack.clipsToBounds = true
hStack.layer.borderWidth = 0.5 hStack.layer.borderWidth = 0.5
hStack.layer.cornerCurve = .continuous
hStack.backgroundColor = inactiveBackgroundColor hStack.backgroundColor = inactiveBackgroundColor
updateBorderColor() updateBorderColor()

View File

@ -33,6 +33,7 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
$0.image = reblogIcon $0.image = reblogIcon
$0.contentMode = .scaleAspectFit $0.contentMode = .scaleAspectFit
$0.layer.masksToBounds = true $0.layer.masksToBounds = true
$0.layer.cornerCurve = .continuous
$0.tintColor = .secondaryLabel $0.tintColor = .secondaryLabel
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
// this needs to be lessThanOrEqualTo not just equalTo b/c otherwise intermediate layouts are broken // this needs to be lessThanOrEqualTo not just equalTo b/c otherwise intermediate layouts are broken
@ -75,6 +76,7 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
private static let avatarImageViewSize: CGFloat = 50 private static let avatarImageViewSize: CGFloat = 50
private(set) lazy var avatarImageView = CachedImageView(cache: .avatars).configure { private(set) lazy var avatarImageView = CachedImageView(cache: .avatars).configure {
$0.layer.masksToBounds = true $0.layer.masksToBounds = true
$0.layer.cornerCurve = .continuous
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
$0.heightAnchor.constraint(equalToConstant: TimelineStatusCollectionViewCell.avatarImageViewSize), $0.heightAnchor.constraint(equalToConstant: TimelineStatusCollectionViewCell.avatarImageViewSize),
$0.widthAnchor.constraint(equalToConstant: TimelineStatusCollectionViewCell.avatarImageViewSize), $0.widthAnchor.constraint(equalToConstant: TimelineStatusCollectionViewCell.avatarImageViewSize),