Compare commits
3 Commits
c94e60d49b
...
8319935a3d
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 8319935a3d | |
Shadowfacts | 91ef386a41 | |
Shadowfacts | c8eec17180 |
|
@ -246,7 +246,7 @@
|
|||
D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B17254254F88B800128392 /* OppositeCollapseKeywordsView.swift */; };
|
||||
D6B22A0F2560D52D004D82EF /* TabbedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B22A0E2560D52D004D82EF /* TabbedPageViewController.swift */; };
|
||||
D6B30E09254BAF63009CAEE5 /* ImageGrayscalifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B30E08254BAF63009CAEE5 /* ImageGrayscalifier.swift */; };
|
||||
D6B4A4FF2506B81A000C81C1 /* AccountDisplayNameLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameLabel.swift */; };
|
||||
D6B4A4FF2506B81A000C81C1 /* AccountDisplayNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameView.swift */; };
|
||||
D6B81F3C2560365300F6E31D /* RefreshableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B81F3B2560365300F6E31D /* RefreshableViewController.swift */; };
|
||||
D6B81F442560390300F6E31D /* MenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B81F432560390300F6E31D /* MenuController.swift */; };
|
||||
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */; };
|
||||
|
@ -302,6 +302,7 @@
|
|||
D6D79F2D2A0D61B400AB2315 /* StatusEditContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F2C2A0D61B400AB2315 /* StatusEditContentTextView.swift */; };
|
||||
D6D79F2F2A0D6A7F00AB2315 /* StatusEditPollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */; };
|
||||
D6D79F532A0FFE3200AB2315 /* ToggleableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */; };
|
||||
D6D79F572A1160B800AB2315 /* AccountDisplayNameLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */; };
|
||||
D6D94955298963A900C59229 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D94954298963A900C59229 /* Colors.swift */; };
|
||||
D6D9498D298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */; };
|
||||
D6D9498F298EB79400C59229 /* CopyableLable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498E298EB79400C59229 /* CopyableLable.swift */; };
|
||||
|
@ -638,7 +639,7 @@
|
|||
D6B17254254F88B800128392 /* OppositeCollapseKeywordsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OppositeCollapseKeywordsView.swift; sourceTree = "<group>"; };
|
||||
D6B22A0E2560D52D004D82EF /* TabbedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabbedPageViewController.swift; sourceTree = "<group>"; };
|
||||
D6B30E08254BAF63009CAEE5 /* ImageGrayscalifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGrayscalifier.swift; sourceTree = "<group>"; };
|
||||
D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDisplayNameLabel.swift; sourceTree = "<group>"; };
|
||||
D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDisplayNameView.swift; sourceTree = "<group>"; };
|
||||
D6B81F3B2560365300F6E31D /* RefreshableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshableViewController.swift; sourceTree = "<group>"; };
|
||||
D6B81F432560390300F6E31D /* MenuController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuController.swift; sourceTree = "<group>"; };
|
||||
D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Visibility.swift"; sourceTree = "<group>"; };
|
||||
|
@ -702,6 +703,7 @@
|
|||
D6D79F2C2A0D61B400AB2315 /* StatusEditContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditContentTextView.swift; sourceTree = "<group>"; };
|
||||
D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditPollView.swift; sourceTree = "<group>"; };
|
||||
D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleableButton.swift; sourceTree = "<group>"; };
|
||||
D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDisplayNameLabel.swift; sourceTree = "<group>"; };
|
||||
D6D94954298963A900C59229 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
|
||||
D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D6D9498E298EB79400C59229 /* CopyableLable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLable.swift; sourceTree = "<group>"; };
|
||||
|
@ -1351,7 +1353,7 @@
|
|||
D6BED1722126661300F02DA0 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameLabel.swift */,
|
||||
D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameView.swift */,
|
||||
D68E6F5E253C9B2D001A1B4C /* BaseEmojiLabel.swift */,
|
||||
D6ADB6EF28ED1F25009924AB /* CachedImageView.swift */,
|
||||
D6895DC328D65342006341DA /* ConfirmReblogStatusPreviewView.swift */,
|
||||
|
@ -1386,6 +1388,7 @@
|
|||
D641C78B213DD92F004B4513 /* Profile Header */,
|
||||
D641C78A213DD926004B4513 /* Status */,
|
||||
D64AAE8F26C80DB600FC57FB /* Toast */,
|
||||
D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
|
@ -2013,7 +2016,7 @@
|
|||
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */,
|
||||
D646DCD22A06F2510059ECEB /* NotificationsCollectionViewController.swift in Sources */,
|
||||
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
|
||||
D6B4A4FF2506B81A000C81C1 /* AccountDisplayNameLabel.swift in Sources */,
|
||||
D6B4A4FF2506B81A000C81C1 /* AccountDisplayNameView.swift in Sources */,
|
||||
D63D8DF42850FE7A008D95E1 /* ViewTags.swift in Sources */,
|
||||
D61DC84B28F4FD2000B82C6E /* ProfileHeaderCollectionViewCell.swift in Sources */,
|
||||
D61A45E828DF477D002BE511 /* LoadingCollectionViewCell.swift in Sources */,
|
||||
|
@ -2109,6 +2112,7 @@
|
|||
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */,
|
||||
D67895C0246870DE00D4CD9E /* LocalAccountAvatarView.swift in Sources */,
|
||||
D6ADB6E828E8C878009924AB /* PublicTimelineDescriptionCollectionViewCell.swift in Sources */,
|
||||
D6D79F572A1160B800AB2315 /* AccountDisplayNameLabel.swift in Sources */,
|
||||
D646DCAC2A06C8840059ECEB /* ProfileFieldValueView.swift in Sources */,
|
||||
D6F4D79429ECB0AF00351B87 /* UIBackgroundConfiguration+AppColors.swift in Sources */,
|
||||
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */,
|
||||
|
|
|
@ -37,6 +37,11 @@ class ImageCache {
|
|||
completion?(entry.data, entry.image)
|
||||
return nil
|
||||
} else {
|
||||
return getFromSource(url, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
func getFromSource(_ url: URL, completion: ((Data?, UIImage?) -> Void)?) -> Request? {
|
||||
return Task.detached(priority: .userInitiated) {
|
||||
let result = await self.fetch(url: url)
|
||||
switch result {
|
||||
|
@ -49,7 +54,6 @@ class ImageCache {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func get(_ url: URL, loadOriginal: Bool = false) async -> (Data?, UIImage?) {
|
||||
if !ImageCache.disableCaching,
|
||||
|
|
|
@ -10,6 +10,7 @@ import UIKit
|
|||
|
||||
extension NSTextAttachment {
|
||||
// Based on https://github.com/ReticentJohn/Amaroq/blob/7c5b7088eb9fd1611dcb0f47d43bf8df093e142c/DireFloof/InlineImageHelpers.m
|
||||
@available(iOS, deprecated: 15.0)
|
||||
convenience init(emojiImage image: UIImage, in font: UIFont, with textColor: UIColor = .label) {
|
||||
let adjustedCapHeight = font.capHeight - 1
|
||||
var imageSizeMatchingFontSize = CGSize(width: image.size.width * (adjustedCapHeight / image.size.height), height: adjustedCapHeight)
|
||||
|
|
|
@ -41,7 +41,7 @@ class ComposeHostingController: UIHostingController<ComposeHostingController.Vie
|
|||
fetchAvatar: { @MainActor in await ImageCache.avatars.get($0).1 },
|
||||
fetchAttachment: { @MainActor in await ImageCache.attachments.get($0).1 },
|
||||
fetchStatus: { mastodonController.persistentContainer.status(for: $0) },
|
||||
displayNameLabel: { AnyView(AccountDisplayNameLabel(account: $0, textStyle: $1, emojiSize: $2)) },
|
||||
displayNameLabel: { AnyView(AccountDisplayNameView(account: $0, textStyle: $1, emojiSize: $2)) },
|
||||
replyContentView: { AnyView(ComposeReplyContentView(status: $0, mastodonController: mastodonController, heightChanged: $1)) },
|
||||
emojiImageView: { AnyView(CustomEmojiImageView(emoji: $0)) }
|
||||
)
|
||||
|
|
|
@ -15,7 +15,7 @@ class FeaturedProfileCollectionViewCell: UICollectionViewCell {
|
|||
@IBOutlet weak var headerImageView: UIImageView!
|
||||
@IBOutlet weak var avatarContainerView: UIView!
|
||||
@IBOutlet weak var avatarImageView: UIImageView!
|
||||
@IBOutlet weak var displayNameLabel: EmojiLabel!
|
||||
@IBOutlet weak var displayNameLabel: AccountDisplayNameLabel!
|
||||
@IBOutlet weak var noteTextView: StatusContentTextView!
|
||||
|
||||
var account: Account?
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
|
@ -47,7 +47,7 @@
|
|||
<constraint firstItem="4wd-wq-Sh2" firstAttribute="centerX" secondItem="RQe-uE-TEv" secondAttribute="centerX" id="bRk-uJ-JGg"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="voW-Is-1b2" customClass="EmojiLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="voW-Is-1b2" customClass="AccountDisplayNameLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="76" y="72" width="316" height="24"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="20"/>
|
||||
<nil key="textColor"/>
|
||||
|
|
|
@ -21,7 +21,7 @@ class SuggestedProfileCardCollectionViewCell: UICollectionViewCell {
|
|||
@IBOutlet weak var headerImageView: CachedImageView!
|
||||
@IBOutlet weak var avatarContainerView: UIView!
|
||||
@IBOutlet weak var avatarImageView: CachedImageView!
|
||||
@IBOutlet weak var displayNameLabel: EmojiLabel!
|
||||
@IBOutlet weak var displayNameLabel: AccountDisplayNameLabel!
|
||||
@IBOutlet weak var usernameLabel: UILabel!
|
||||
@IBOutlet weak var noteTextView: StatusContentTextView!
|
||||
@IBOutlet weak var suggestionSourceButton: UIButton!
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="collection view cell content view" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
|
@ -44,7 +44,7 @@
|
|||
<constraint firstAttribute="width" constant="90" id="wav-YT-e4Y"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsLetterSpacingToFitWidth="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XCk-sZ-ujT" customClass="EmojiLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsLetterSpacingToFitWidth="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XCk-sZ-ujT" customClass="AccountDisplayNameLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="106" y="100" width="333" height="29"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="24"/>
|
||||
<nil key="textColor"/>
|
||||
|
|
|
@ -65,7 +65,7 @@ struct MuteAccountView: View {
|
|||
)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
AccountDisplayNameLabel(account: account, textStyle: .headline, emojiSize: 17)
|
||||
AccountDisplayNameView(account: account, textStyle: .headline, emojiSize: 17)
|
||||
Text("@\(account.acct)")
|
||||
.fontWeight(.light)
|
||||
.foregroundColor(.secondary)
|
||||
|
|
|
@ -55,7 +55,7 @@ struct ReportView: View {
|
|||
)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
AccountDisplayNameLabel(account: account, textStyle: .headline, emojiSize: 17)
|
||||
AccountDisplayNameView(account: account, textStyle: .headline, emojiSize: 17)
|
||||
Text("@\(account.acct)")
|
||||
.fontWeight(.light)
|
||||
.foregroundColor(.secondary)
|
||||
|
|
|
@ -15,7 +15,7 @@ class AccountTableViewCell: UITableViewCell {
|
|||
var mastodonController: MastodonController! { delegate?.apiController }
|
||||
|
||||
@IBOutlet weak var avatarImageView: UIImageView!
|
||||
@IBOutlet weak var displayNameLabel: EmojiLabel!
|
||||
@IBOutlet weak var displayNameLabel: AccountDisplayNameLabel!
|
||||
@IBOutlet weak var usernameLabel: UILabel!
|
||||
@IBOutlet weak var noteLabel: EmojiLabel!
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
|
@ -28,7 +28,7 @@
|
|||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="Iif-9m-vM5">
|
||||
<rect key="frame" x="74" y="11" width="230" height="78"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Fhc-bZ-lkB" customClass="EmojiLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Fhc-bZ-lkB" customClass="AccountDisplayNameLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="230" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
|
|
|
@ -12,7 +12,7 @@ import Pachyderm
|
|||
class LargeAccountDetailView: UIView {
|
||||
|
||||
var avatarImageView = UIImageView()
|
||||
var displayNameLabel = EmojiLabel()
|
||||
var displayNameLabel = AccountDisplayNameLabel()
|
||||
var usernameLabel = UILabel()
|
||||
|
||||
var avatarRequest: ImageCache.Request?
|
||||
|
|
|
@ -2,106 +2,31 @@
|
|||
// AccountDisplayNameLabel.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 9/7/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
// Created by Shadowfacts on 5/14/23.
|
||||
// Copyright © 2023 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
|
||||
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
|
||||
class AccountDisplayNameLabel: EmojiLabel {
|
||||
|
||||
struct AccountDisplayNameLabel: View {
|
||||
let account: any AccountProtocol
|
||||
let textStyle: Font.TextStyle
|
||||
@ScaledMetric var emojiSize: CGFloat
|
||||
@State var text: Text
|
||||
@State var emojiRequests = [ImageCache.Request]()
|
||||
private var accountID: String?
|
||||
// store the display name, so that if it changes the label updates w/o changing the id
|
||||
private var accountDisplayName: String?
|
||||
|
||||
init(account: any AccountProtocol, textStyle: Font.TextStyle, emojiSize: CGFloat) {
|
||||
self.account = account
|
||||
self.textStyle = textStyle
|
||||
self._emojiSize = ScaledMetric(wrappedValue: emojiSize, relativeTo: textStyle)
|
||||
let name = account.displayName.isEmpty ? account.username : account.displayName
|
||||
self._text = State(initialValue: Text(verbatim: name))
|
||||
func updateForAccountDisplayName(account: some AccountProtocol) {
|
||||
guard accountID != account.id || accountDisplayName != account.displayName || Preferences.shared.hideCustomEmojiInUsernames == hasEmojis else {
|
||||
return
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
text
|
||||
.font(.system(textStyle).weight(.semibold))
|
||||
.onAppear(perform: self.loadEmojis)
|
||||
}
|
||||
|
||||
private func loadEmojis() {
|
||||
let fullRange = NSRange(account.displayName.startIndex..., in: account.displayName)
|
||||
let matches = emojiRegex.matches(in: account.displayName, options: [], range: fullRange)
|
||||
guard !matches.isEmpty else { return }
|
||||
|
||||
let emojiImages = MultiThreadDictionary<String, Image>()
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for emoji in account.emojis {
|
||||
guard matches.contains(where: { (match) in
|
||||
let matchShortcode = (account.displayName as NSString).substring(with: match.range(at: 1))
|
||||
return emoji.shortcode == matchShortcode
|
||||
}) else {
|
||||
continue
|
||||
}
|
||||
|
||||
group.enter()
|
||||
let request = ImageCache.emojis.get(URL(emoji.url)!) { (_, image) in
|
||||
defer { group.leave() }
|
||||
guard let image = image else { return }
|
||||
|
||||
let size = CGSize(width: emojiSize, height: emojiSize)
|
||||
let renderer = UIGraphicsImageRenderer(size: size)
|
||||
let resized = renderer.image { (ctx) in
|
||||
image.draw(in: CGRect(origin: .zero, size: size))
|
||||
}
|
||||
|
||||
emojiImages[emoji.shortcode] = Image(uiImage: resized)
|
||||
}
|
||||
if let request = request {
|
||||
emojiRequests.append(request)
|
||||
}
|
||||
}
|
||||
|
||||
group.notify(queue: .main) {
|
||||
var text: Text?
|
||||
|
||||
var endIndex = account.displayName.utf16.count
|
||||
|
||||
// iterate backwards as to not alter the indices of earlier matches
|
||||
for match in matches.reversed() {
|
||||
let shortcode = (account.displayName as NSString).substring(with: match.range(at: 1))
|
||||
guard let image = emojiImages[shortcode] else { continue }
|
||||
|
||||
let afterCurrentMatch = (account.displayName as NSString).substring(with: NSRange(location: match.range.upperBound, length: endIndex - match.range.upperBound))
|
||||
|
||||
if let subsequent = text {
|
||||
text = Text(image) + Text(verbatim: afterCurrentMatch) + subsequent
|
||||
accountID = account.id
|
||||
accountDisplayName = account.displayName
|
||||
self.text = accountDisplayName
|
||||
if Preferences.shared.hideCustomEmojiInUsernames {
|
||||
self.removeEmojis()
|
||||
} else {
|
||||
text = Text(image) + Text(verbatim: afterCurrentMatch)
|
||||
self.setEmojis(account.emojis, identifier: account.id)
|
||||
}
|
||||
}
|
||||
|
||||
endIndex = match.range.lowerBound
|
||||
}
|
||||
|
||||
let beforeLastMatch = (account.displayName as NSString).substring(to: endIndex)
|
||||
|
||||
if let text = text {
|
||||
self.text = Text(verbatim: beforeLastMatch) + text
|
||||
} else {
|
||||
self.text = Text(verbatim: beforeLastMatch)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//struct AccountDisplayNameLabel_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// AccountDisplayNameLabel()
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
//
|
||||
// AccountDisplayNameView.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 9/7/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
|
||||
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
|
||||
|
||||
struct AccountDisplayNameView: View {
|
||||
let account: any AccountProtocol
|
||||
let textStyle: Font.TextStyle
|
||||
@ScaledMetric var emojiSize: CGFloat
|
||||
@State var text: Text
|
||||
@State var emojiRequests = [ImageCache.Request]()
|
||||
|
||||
init(account: any AccountProtocol, textStyle: Font.TextStyle, emojiSize: CGFloat) {
|
||||
self.account = account
|
||||
self.textStyle = textStyle
|
||||
self._emojiSize = ScaledMetric(wrappedValue: emojiSize, relativeTo: textStyle)
|
||||
let name = account.displayName.isEmpty ? account.username : account.displayName
|
||||
self._text = State(initialValue: Text(verbatim: name))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
text
|
||||
.font(.system(textStyle).weight(.semibold))
|
||||
.onAppear(perform: self.loadEmojis)
|
||||
}
|
||||
|
||||
private func loadEmojis() {
|
||||
let fullRange = NSRange(account.displayName.startIndex..., in: account.displayName)
|
||||
let matches = emojiRegex.matches(in: account.displayName, options: [], range: fullRange)
|
||||
guard !matches.isEmpty else { return }
|
||||
|
||||
let emojiImages = MultiThreadDictionary<String, Image>()
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for emoji in account.emojis {
|
||||
guard matches.contains(where: { (match) in
|
||||
let matchShortcode = (account.displayName as NSString).substring(with: match.range(at: 1))
|
||||
return emoji.shortcode == matchShortcode
|
||||
}) else {
|
||||
continue
|
||||
}
|
||||
|
||||
group.enter()
|
||||
let request = ImageCache.emojis.get(URL(emoji.url)!) { (_, image) in
|
||||
defer { group.leave() }
|
||||
guard let image = image else { return }
|
||||
|
||||
let size = CGSize(width: emojiSize, height: emojiSize)
|
||||
let renderer = UIGraphicsImageRenderer(size: size)
|
||||
let resized = renderer.image { (ctx) in
|
||||
image.draw(in: CGRect(origin: .zero, size: size))
|
||||
}
|
||||
|
||||
emojiImages[emoji.shortcode] = Image(uiImage: resized)
|
||||
}
|
||||
if let request = request {
|
||||
emojiRequests.append(request)
|
||||
}
|
||||
}
|
||||
|
||||
group.notify(queue: .main) {
|
||||
var text: Text?
|
||||
|
||||
var endIndex = account.displayName.utf16.count
|
||||
|
||||
// iterate backwards as to not alter the indices of earlier matches
|
||||
for match in matches.reversed() {
|
||||
let shortcode = (account.displayName as NSString).substring(with: match.range(at: 1))
|
||||
guard let image = emojiImages[shortcode] else { continue }
|
||||
|
||||
let afterCurrentMatch = (account.displayName as NSString).substring(with: NSRange(location: match.range.upperBound, length: endIndex - match.range.upperBound))
|
||||
|
||||
if let subsequent = text {
|
||||
text = Text(image) + Text(verbatim: afterCurrentMatch) + subsequent
|
||||
} else {
|
||||
text = Text(image) + Text(verbatim: afterCurrentMatch)
|
||||
}
|
||||
|
||||
endIndex = match.range.lowerBound
|
||||
}
|
||||
|
||||
let beforeLastMatch = (account.displayName as NSString).substring(to: endIndex)
|
||||
|
||||
if let text = text {
|
||||
self.text = Text(verbatim: beforeLastMatch) + text
|
||||
} else {
|
||||
self.text = Text(verbatim: beforeLastMatch)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//struct AccountDisplayNameLabel_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// AccountDisplayNameView()
|
||||
// }
|
||||
//}
|
|
@ -40,6 +40,16 @@ extension BaseEmojiLabel {
|
|||
return
|
||||
}
|
||||
|
||||
// Based on https://github.com/ReticentJohn/Amaroq/blob/7c5b7088eb9fd1611dcb0f47d43bf8df093e142c/DireFloof/InlineImageHelpers.m
|
||||
let adjustedCapHeight = emojiFont.capHeight - 1
|
||||
func emojiImageSize(_ image: UIImage) -> CGSize {
|
||||
var imageSizeMatchingFontSize = CGSize(width: image.size.width * (adjustedCapHeight / image.size.height), height: adjustedCapHeight)
|
||||
var scale: CGFloat = 1.4
|
||||
scale *= UIScreen.main.scale
|
||||
imageSizeMatchingFontSize = CGSize(width: imageSizeMatchingFontSize.width * scale, height: imageSizeMatchingFontSize.height * scale)
|
||||
return imageSizeMatchingFontSize
|
||||
}
|
||||
|
||||
let emojiImages = MultiThreadDictionary<String, UIImage>()
|
||||
var foundEmojis = false
|
||||
|
||||
|
@ -57,22 +67,36 @@ extension BaseEmojiLabel {
|
|||
foundEmojis = true
|
||||
|
||||
if let image = ImageCache.emojis.get(URL(emoji.url)!)?.image {
|
||||
// if the image is cached, add it immediately
|
||||
emojiImages[emoji.shortcode] = image
|
||||
// if the image is cached, add it immediately.
|
||||
// we generate the thumbnail on the main thread, because it's usually fast enough
|
||||
// and the delay caused by doing it asynchronously looks works.
|
||||
// todo: consider caching these somewhere? the cache needs to take into account both the url and the font size, which may vary across labels, so can't just use ImageCache
|
||||
if let thumbnail = image.preparingThumbnail(of: emojiImageSize(image)),
|
||||
let cgImage = thumbnail.cgImage {
|
||||
// the thumbnail API takes a pixel size and returns an image with scale 1, but we want the actual screen scale, so convert
|
||||
// see FB12187798
|
||||
emojiImages[emoji.shortcode] = UIImage(cgImage: cgImage, scale: UIScreen.main.scale, orientation: .up)
|
||||
}
|
||||
} else {
|
||||
// otherwise, perform the network request
|
||||
|
||||
group.enter()
|
||||
// todo: ImageCache.emojis.get here will re-check the memory and disk caches, there should be another method to force-refetch
|
||||
let request = ImageCache.emojis.get(URL(emoji.url)!) { (_, image) in
|
||||
guard let image = image,
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: URL(emoji.url)!, image: image) else {
|
||||
let request = ImageCache.emojis.getFromSource(URL(emoji.url)!) { (_, image) in
|
||||
guard let image else {
|
||||
group.leave()
|
||||
return
|
||||
}
|
||||
image.prepareThumbnail(of: emojiImageSize(image)) { thumbnail in
|
||||
guard let thumbnail = thumbnail?.cgImage,
|
||||
case let rescaled = UIImage(cgImage: thumbnail, scale: UIScreen.main.scale, orientation: .up),
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: URL(emoji.url)!, image: rescaled) else {
|
||||
group.leave()
|
||||
return
|
||||
}
|
||||
emojiImages[emoji.shortcode] = transformedImage
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
if let request = request {
|
||||
emojiRequests.append(request)
|
||||
}
|
||||
|
@ -91,10 +115,8 @@ extension BaseEmojiLabel {
|
|||
// even though the closures is invoked on the same thread that withLock is called, so it's unclear why it needs to be @Sendable (FB11494878)
|
||||
// so, just ignore the warnings
|
||||
let emojiAttachments = emojiImages.withLock {
|
||||
let emojiFont = self.emojiFont
|
||||
let emojiTextColor = self.emojiTextColor
|
||||
return $0.mapValues { image in
|
||||
NSTextAttachment(emojiImage: image, in: emojiFont, with: emojiTextColor)
|
||||
NSTextAttachment(image: image)
|
||||
}
|
||||
}
|
||||
let placeholder = usePlaceholders ? NSTextAttachment(emojiPlaceholderIn: self.emojiFont) : nil
|
||||
|
|
|
@ -52,7 +52,7 @@ class ConfirmReblogStatusPreviewView: UIView {
|
|||
vStack.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||
hStack.addArrangedSubview(vStack)
|
||||
|
||||
let displayNameLabel = EmojiLabel()
|
||||
let displayNameLabel = AccountDisplayNameLabel()
|
||||
displayNameLabel.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .caption1).addingAttributes([.traits: [UIFontDescriptor.TraitKey.weight: UIFont.Weight.semibold]]), size: 0)
|
||||
displayNameLabel.adjustsFontSizeToFitWidth = true
|
||||
displayNameLabel.adjustsFontForContentSizeCategory = true
|
||||
|
|
|
@ -38,24 +38,3 @@ class EmojiLabel: UILabel, BaseEmojiLabel {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
extension EmojiLabel {
|
||||
func updateForAccountDisplayName(account: Account) {
|
||||
if Preferences.shared.hideCustomEmojiInUsernames {
|
||||
self.text = account.displayName
|
||||
self.removeEmojis()
|
||||
} else {
|
||||
self.text = account.displayName
|
||||
self.setEmojis(account.emojis, identifier: account.id)
|
||||
}
|
||||
}
|
||||
func updateForAccountDisplayName(account: AccountMO) {
|
||||
if Preferences.shared.hideCustomEmojiInUsernames {
|
||||
self.text = account.displayNameWithoutCustomEmoji
|
||||
self.removeEmojis()
|
||||
} else {
|
||||
self.text = account.displayOrUserName
|
||||
self.setEmojis(account.emojis, identifier: account.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class ProfileHeaderView: UIView {
|
|||
@IBOutlet weak var avatarImageView: UIImageView!
|
||||
@IBOutlet weak var moreButton: ProfileHeaderButton!
|
||||
@IBOutlet weak var followButton: ProfileHeaderButton!
|
||||
@IBOutlet weak var displayNameLabel: EmojiLabel!
|
||||
@IBOutlet weak var displayNameLabel: AccountDisplayNameLabel!
|
||||
@IBOutlet weak var usernameLabel: UILabel!
|
||||
@IBOutlet weak var lockImageView: UIImageView!
|
||||
@IBOutlet weak var vStack: UIStackView!
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<constraint firstItem="TkY-oK-if4" firstAttribute="centerX" secondItem="wT9-2J-uSY" secondAttribute="centerX" id="ozz-sa-gSc"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vcl-Gl-kXl" customClass="EmojiLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vcl-Gl-kXl" customClass="AccountDisplayNameLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="144" y="206" width="254" height="29"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="24"/>
|
||||
<nil key="textColor"/>
|
||||
|
|
|
@ -39,7 +39,7 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
|
|||
$0.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed)))
|
||||
}
|
||||
|
||||
let displayNameLabel = EmojiLabel().configure {
|
||||
let displayNameLabel = AccountDisplayNameLabel().configure {
|
||||
$0.font = UIFontMetrics(forTextStyle: .title1).scaledFont(for: .systemFont(ofSize: 24, weight: .semibold))
|
||||
$0.adjustsFontForContentSizeCategory = true
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ protocol StatusCollectionViewCellDelegate: AnyObject, TuskerNavigationDelegate,
|
|||
protocol StatusCollectionViewCell: UICollectionViewCell, AttachmentViewDelegate {
|
||||
// MARK: Subviews
|
||||
var avatarImageView: CachedImageView { get }
|
||||
var displayNameLabel: EmojiLabel { get }
|
||||
var displayNameLabel: AccountDisplayNameLabel { get }
|
||||
var usernameLabel: UILabel { get }
|
||||
var contentWarningLabel: EmojiLabel { get }
|
||||
var collapseButton: StatusCollapseButton { get }
|
||||
|
|
|
@ -114,7 +114,7 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
|||
$0.spacing = 4
|
||||
}
|
||||
|
||||
let displayNameLabel = EmojiLabel().configure {
|
||||
let displayNameLabel = AccountDisplayNameLabel().configure {
|
||||
$0.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .body).addingAttributes([
|
||||
.traits: [
|
||||
UIFontDescriptor.TraitKey.weight: UIFont.Weight.semibold.rawValue,
|
||||
|
@ -619,10 +619,6 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
|||
metaIndicatorsView.updateUI(status: status)
|
||||
|
||||
timelineReasonIcon.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * Self.timelineReasonIconSize
|
||||
if let rebloggerID,
|
||||
let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) {
|
||||
updateRebloggerLabel(reblogger: reblogger)
|
||||
}
|
||||
}
|
||||
|
||||
func updateStatusState(status: StatusMO) {
|
||||
|
@ -703,6 +699,11 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
|||
|
||||
updateUIForPreferences(status: status)
|
||||
|
||||
if let rebloggerID,
|
||||
let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) {
|
||||
updateRebloggerLabel(reblogger: reblogger)
|
||||
}
|
||||
|
||||
if isGrayscale != Preferences.shared.grayscaleImages {
|
||||
updateGrayscaleableUI(status: status)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue