Compare commits
4 Commits
1bc598691f
...
a363308147
Author | SHA1 | Date |
---|---|---|
Shadowfacts | a363308147 | |
Shadowfacts | 1618313742 | |
Shadowfacts | e53b14c729 | |
Shadowfacts | d3848b356b |
|
@ -38,3 +38,5 @@ extension Notification {
|
|||
case follow
|
||||
}
|
||||
}
|
||||
|
||||
extension Notification: Identifiable {}
|
||||
|
|
|
@ -129,3 +129,5 @@ extension Status {
|
|||
case direct
|
||||
}
|
||||
}
|
||||
|
||||
extension Status: Identifiable {}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// NotificationGroup.swift
|
||||
// Pachyderm
|
||||
//
|
||||
// Created by Shadowfacts on 9/5/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class NotificationGroup {
|
||||
public let notificationIDs: [String]
|
||||
public let id: String
|
||||
public let kind: Notification.Kind
|
||||
|
||||
init?(notifications: [Notification]) {
|
||||
guard !notifications.isEmpty else { return nil }
|
||||
self.notificationIDs = notifications.map { $0.id }
|
||||
self.id = notifications.first!.id
|
||||
self.kind = notifications.first!.kind
|
||||
}
|
||||
|
||||
public static func createGroups(notifications: [Notification], only allowedTypes: [Notification.Kind]) -> [NotificationGroup] {
|
||||
return notifications.reduce(into: [[Notification]]()) { (groups, notification) in
|
||||
if allowedTypes.contains(notification.kind),
|
||||
let lastGroup = groups.last,
|
||||
let firstStatus = lastGroup.first,
|
||||
firstStatus.kind == notification.kind,
|
||||
firstStatus.status?.id == notification.status?.id {
|
||||
|
||||
groups[groups.count - 1].append(notification)
|
||||
} else {
|
||||
groups.append([notification])
|
||||
}
|
||||
}.map {
|
||||
NotificationGroup(notifications: $0)!
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension NotificationGroup: Identifiable {}
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public struct TimelineSegment<Type: Identifiable> {
|
||||
public struct TimelineSegment<Type: Identifiable> where Type.ID == String {
|
||||
private var ids: [String]
|
||||
|
||||
public init(objects: [Type]) {
|
||||
|
@ -47,11 +47,3 @@ extension TimelineSegment: RandomAccessCollection {
|
|||
return ids.endIndex
|
||||
}
|
||||
}
|
||||
|
||||
// todo: remove me when i update to beta 5, Identifiable is now part of Swift stdlib
|
||||
public protocol Identifiable {
|
||||
var id: String { get }
|
||||
}
|
||||
|
||||
extension Status: Identifiable {}
|
||||
extension Notification: Identifiable {}
|
|
@ -86,10 +86,6 @@
|
|||
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; };
|
||||
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */ = {isa = PBXBuildFile; fileRef = D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */; };
|
||||
D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D641C772213CAA25004B4513 /* NotificationsTableViewController.swift */; };
|
||||
D641C777213CAA9E004B4513 /* ActionNotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D641C776213CAA9E004B4513 /* ActionNotificationTableViewCell.swift */; };
|
||||
D641C779213CAC56004B4513 /* ActionNotificationTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D641C778213CAC56004B4513 /* ActionNotificationTableViewCell.xib */; };
|
||||
D641C77B213CB017004B4513 /* FollowNotificationTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D641C77A213CB017004B4513 /* FollowNotificationTableViewCell.xib */; };
|
||||
D641C77D213CB024004B4513 /* FollowNotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D641C77C213CB024004B4513 /* FollowNotificationTableViewCell.swift */; };
|
||||
D641C77F213DC78A004B4513 /* InlineTextAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */; };
|
||||
D6434EB3215B1856001A919A /* XCBRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6434EB2215B1856001A919A /* XCBRequest.swift */; };
|
||||
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */; };
|
||||
|
@ -146,6 +142,12 @@
|
|||
D68632AA21ED8319008C716E /* GMGridViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D686329021ED8319008C716E /* GMGridViewController.m */; };
|
||||
D68632AB21ED8319008C716E /* GMImagePickerController.h in Headers */ = {isa = PBXBuildFile; fileRef = D686329121ED8319008C716E /* GMImagePickerController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D68632AC21ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686329321ED8319008C716E /* GMImagePicker.strings */; };
|
||||
D6A3BC7723218E1300FD64D5 /* TimelineSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */; };
|
||||
D6A3BC7923218E9200FD64D5 /* NotificationGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */; };
|
||||
D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */; };
|
||||
D6A3BC7D232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC7B232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib */; };
|
||||
D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7E2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift */; };
|
||||
D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC7F2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib */; };
|
||||
D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */; };
|
||||
D6A5FAFB217B86CE003DB2D9 /* OnboardingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */; };
|
||||
D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */; };
|
||||
|
@ -173,7 +175,6 @@
|
|||
D6D4DDE5212518A200E1C4BB /* TuskerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4DDE4212518A200E1C4BB /* TuskerTests.swift */; };
|
||||
D6D4DDF0212518A200E1C4BB /* TuskerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4DDEF212518A200E1C4BB /* TuskerUITests.swift */; };
|
||||
D6D58DF922074B74009C8DD9 /* LinkLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D58DF822074B74009C8DD9 /* LinkLabel.swift */; };
|
||||
D6DD353B22F25D2E00A9563A /* TimelineSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD353A22F25D2E00A9563A /* TimelineSegment.swift */; };
|
||||
D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD353C22F28CD000A9563A /* ContentWarningCopyMode.swift */; };
|
||||
D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD353E22F502EC00A9563A /* Preferences+Notification.swift */; };
|
||||
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0DC8D216EDF1E00369478 /* Previewing.swift */; };
|
||||
|
@ -327,10 +328,6 @@
|
|||
D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = "<group>"; };
|
||||
D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = DomainBlocks.plist; sourceTree = "<group>"; };
|
||||
D641C772213CAA25004B4513 /* NotificationsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsTableViewController.swift; sourceTree = "<group>"; };
|
||||
D641C776213CAA9E004B4513 /* ActionNotificationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionNotificationTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D641C778213CAC56004B4513 /* ActionNotificationTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ActionNotificationTableViewCell.xib; sourceTree = "<group>"; };
|
||||
D641C77A213CB017004B4513 /* FollowNotificationTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FollowNotificationTableViewCell.xib; sourceTree = "<group>"; };
|
||||
D641C77C213CB024004B4513 /* FollowNotificationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowNotificationTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineTextAttachment.swift; sourceTree = "<group>"; };
|
||||
D6434EB2215B1856001A919A /* XCBRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBRequest.swift; sourceTree = "<group>"; };
|
||||
D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageExpandAnimationController.swift; sourceTree = "<group>"; };
|
||||
|
@ -386,6 +383,12 @@
|
|||
D686329021ED8319008C716E /* GMGridViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMGridViewController.m; sourceTree = "<group>"; };
|
||||
D686329121ED8319008C716E /* GMImagePickerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMImagePickerController.h; sourceTree = "<group>"; };
|
||||
D686329421ED8319008C716E /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
||||
D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSegment.swift; sourceTree = "<group>"; };
|
||||
D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationGroup.swift; sourceTree = "<group>"; };
|
||||
D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionNotificationGroupTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D6A3BC7B232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ActionNotificationGroupTableViewCell.xib; sourceTree = "<group>"; };
|
||||
D6A3BC7E2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowNotificationGroupTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D6A3BC7F2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FollowNotificationGroupTableViewCell.xib; sourceTree = "<group>"; };
|
||||
D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeViewController.xib; sourceTree = "<group>"; };
|
||||
D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OnboardingViewController.xib; sourceTree = "<group>"; };
|
||||
D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIActivity+Types.swift"; sourceTree = "<group>"; };
|
||||
|
@ -418,7 +421,6 @@
|
|||
D6D4DDEF212518A200E1C4BB /* TuskerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerUITests.swift; sourceTree = "<group>"; };
|
||||
D6D4DDF1212518A200E1C4BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D6D58DF822074B74009C8DD9 /* LinkLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkLabel.swift; sourceTree = "<group>"; };
|
||||
D6DD353A22F25D2E00A9563A /* TimelineSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TimelineSegment.swift; path = ../../../../../../System/Volumes/Data/Users/shadowfacts/Dev/iOS/Tusker/Pachyderm/TimelineSegment.swift; sourceTree = "<group>"; };
|
||||
D6DD353C22F28CD000A9563A /* ContentWarningCopyMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ContentWarningCopyMode.swift; path = ../../../../../../../System/Volumes/Data/Users/shadowfacts/Dev/iOS/Tusker/Tusker/Preferences/ContentWarningCopyMode.swift; sourceTree = "<group>"; };
|
||||
D6DD353E22F502EC00A9563A /* Preferences+Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Preferences+Notification.swift"; path = "../../../../../../../System/Volumes/Data/Users/shadowfacts/Dev/iOS/Tusker/Tusker/Preferences/Preferences+Notification.swift"; sourceTree = "<group>"; };
|
||||
D6E0DC8D216EDF1E00369478 /* Previewing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Previewing.swift; sourceTree = "<group>"; };
|
||||
|
@ -557,8 +559,7 @@
|
|||
D61099AE2144B0CC00432DC2 /* Info.plist */,
|
||||
D61099C82144B13C00432DC2 /* Client.swift */,
|
||||
D6109A0A2145953C00432DC2 /* ClientModel.swift */,
|
||||
D6E6F26221603F8B006A8599 /* CharacterCounter.swift */,
|
||||
D6DD353A22F25D2E00A9563A /* TimelineSegment.swift */,
|
||||
D6A3BC7223218C6E00FD64D5 /* Utilities */,
|
||||
D61099D72144B74500432DC2 /* Extensions */,
|
||||
D61099CC2144B2C300432DC2 /* Request */,
|
||||
D61099DA2144BDB600432DC2 /* Response */,
|
||||
|
@ -784,10 +785,10 @@
|
|||
D641C78C213DD937004B4513 /* Notifications */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D641C778213CAC56004B4513 /* ActionNotificationTableViewCell.xib */,
|
||||
D641C776213CAA9E004B4513 /* ActionNotificationTableViewCell.swift */,
|
||||
D641C77A213CB017004B4513 /* FollowNotificationTableViewCell.xib */,
|
||||
D641C77C213CB024004B4513 /* FollowNotificationTableViewCell.swift */,
|
||||
D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */,
|
||||
D6A3BC7B232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib */,
|
||||
D6A3BC7E2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift */,
|
||||
D6A3BC7F2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib */,
|
||||
);
|
||||
path = Notifications;
|
||||
sourceTree = "<group>";
|
||||
|
@ -930,6 +931,16 @@
|
|||
path = de.lproj;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D6A3BC7223218C6E00FD64D5 /* Utilities */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D6E6F26221603F8B006A8599 /* CharacterCounter.swift */,
|
||||
D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */,
|
||||
D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */,
|
||||
);
|
||||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D6AEBB3F2321640F00E5038B /* Activities */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1327,19 +1338,19 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D641C779213CAC56004B4513 /* ActionNotificationTableViewCell.xib in Resources */,
|
||||
D627FF7D217E958900CC0648 /* DraftTableViewCell.xib in Resources */,
|
||||
D6A3BC7D232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib in Resources */,
|
||||
D667E5E921349EE50057A976 /* ProfileHeaderTableViewCell.xib in Resources */,
|
||||
D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */,
|
||||
D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */,
|
||||
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */,
|
||||
D6A5FAFB217B86CE003DB2D9 /* OnboardingViewController.xib in Resources */,
|
||||
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */,
|
||||
D641C77B213CB017004B4513 /* FollowNotificationTableViewCell.xib in Resources */,
|
||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */,
|
||||
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */,
|
||||
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */,
|
||||
0411610122B442870030A9B7 /* AttachmentViewController.xib in Resources */,
|
||||
D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */,
|
||||
D60C07E421E8176B0057FAA8 /* ComposeMediaView.xib in Resources */,
|
||||
D667E5E12134937B0057A976 /* StatusTableViewCell.xib in Resources */,
|
||||
D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */,
|
||||
|
@ -1403,8 +1414,9 @@
|
|||
D6109A032145722C00432DC2 /* RegisteredApplication.swift in Sources */,
|
||||
D6109A0921458C4A00432DC2 /* Empty.swift in Sources */,
|
||||
D6285B4F21EA695800FE4B39 /* StatusContentType.swift in Sources */,
|
||||
D6A3BC7923218E9200FD64D5 /* NotificationGroup.swift in Sources */,
|
||||
D61099DF2144C11400432DC2 /* MastodonError.swift in Sources */,
|
||||
D6DD353B22F25D2E00A9563A /* TimelineSegment.swift in Sources */,
|
||||
D6A3BC7723218E1300FD64D5 /* TimelineSegment.swift in Sources */,
|
||||
D61099D62144B4B200432DC2 /* FormAttachment.swift in Sources */,
|
||||
D6109A072145756700432DC2 /* LoginSettings.swift in Sources */,
|
||||
D61099ED2145664800432DC2 /* Filter.swift in Sources */,
|
||||
|
@ -1449,6 +1461,7 @@
|
|||
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
||||
D66362712136338600C9CBA2 /* ComposeViewController.swift in Sources */,
|
||||
D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */,
|
||||
D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */,
|
||||
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */,
|
||||
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */,
|
||||
D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */,
|
||||
|
@ -1485,7 +1498,6 @@
|
|||
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */,
|
||||
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */,
|
||||
D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */,
|
||||
D641C777213CAA9E004B4513 /* ActionNotificationTableViewCell.swift in Sources */,
|
||||
D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */,
|
||||
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */,
|
||||
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */,
|
||||
|
@ -1496,11 +1508,11 @@
|
|||
D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */,
|
||||
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */,
|
||||
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */,
|
||||
D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */,
|
||||
D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */,
|
||||
04586B4122B2FFB10021BD04 /* PreferencesView.swift in Sources */,
|
||||
D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */,
|
||||
04D14BB022B34A2800642648 /* GalleryViewController.swift in Sources */,
|
||||
D641C77D213CB024004B4513 /* FollowNotificationTableViewCell.swift in Sources */,
|
||||
D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */,
|
||||
D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */,
|
||||
D6F1F84D2193B56E00F5FE67 /* Cache.swift in Sources */,
|
||||
|
|
|
@ -11,7 +11,13 @@ import Pachyderm
|
|||
|
||||
class NotificationsTableViewController: EnhancedTableViewController {
|
||||
|
||||
var timelineSegments: [TimelineSegment<Pachyderm.Notification>] = [] {
|
||||
private let statusCell = "statusCell"
|
||||
private let actionGroupCell = "actionGroupCell"
|
||||
private let followGroupCell = "followGroupCell"
|
||||
|
||||
let groupTypes = [Notification.Kind.favourite, .reblog, .follow]
|
||||
|
||||
var groups: [NotificationGroup] = [] {
|
||||
didSet {
|
||||
DispatchQueue.main.async {
|
||||
self.tableView.reloadData()
|
||||
|
@ -42,9 +48,9 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
|||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.estimatedRowHeight = 140
|
||||
|
||||
tableView.register(UINib(nibName: "StatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell")
|
||||
tableView.register(UINib(nibName: "ActionNotificationTableViewCell", bundle: nil), forCellReuseIdentifier: "actionCell")
|
||||
tableView.register(UINib(nibName: "FollowNotificationTableViewCell", bundle: nil), forCellReuseIdentifier: "followCell")
|
||||
tableView.register(UINib(nibName: "StatusTableViewCell", bundle: nil), forCellReuseIdentifier: statusCell)
|
||||
tableView.register(UINib(nibName: "ActionNotificationGroupTableViewCell", bundle: nil), forCellReuseIdentifier: actionGroupCell)
|
||||
tableView.register(UINib(nibName: "FollowNotificationGroupTableViewCell", bundle: nil), forCellReuseIdentifier: followGroupCell)
|
||||
|
||||
tableView.prefetchDataSource = self
|
||||
|
||||
|
@ -52,7 +58,10 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
|||
MastodonController.client.run(request) { result in
|
||||
guard case let .success(notifications, pagination) = result else { fatalError() }
|
||||
|
||||
self.timelineSegments.append(TimelineSegment(objects: notifications))
|
||||
let groups = NotificationGroup.createGroups(notifications: notifications, only: self.groupTypes)
|
||||
|
||||
self.groups.append(contentsOf: groups)
|
||||
|
||||
MastodonCache.addAll(notifications: notifications)
|
||||
MastodonCache.addAll(statuses: notifications.compactMap { $0.status })
|
||||
MastodonCache.addAll(accounts: notifications.map { $0.account })
|
||||
|
@ -67,32 +76,36 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
|||
// MARK: - Table view data source
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return timelineSegments.count
|
||||
return 1
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return timelineSegments[section].count
|
||||
return groups.count
|
||||
}
|
||||
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let notificationID = timelineSegments[indexPath.section][indexPath.row]
|
||||
guard let notification = MastodonCache.notification(for: notificationID) else { fatalError() }
|
||||
let group = groups[indexPath.row]
|
||||
|
||||
switch notification.kind {
|
||||
switch group.kind {
|
||||
case .mention:
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() }
|
||||
guard let notification = MastodonCache.notification(for: group.notificationIDs.first!),
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as? StatusTableViewCell else {
|
||||
fatalError()
|
||||
}
|
||||
cell.updateUI(statusID: notification.status!.id)
|
||||
cell.delegate = self
|
||||
return cell
|
||||
|
||||
case .favourite, .reblog:
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "actionCell", for: indexPath) as? ActionNotificationTableViewCell else { fatalError() }
|
||||
cell.updateUI(for: notification)
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: actionGroupCell, for: indexPath) as? ActionNotificationGroupTableViewCell else { fatalError() }
|
||||
cell.updateUI(group: group)
|
||||
cell.delegate = self
|
||||
return cell
|
||||
|
||||
case .follow:
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "followCell", for: indexPath) as? FollowNotificationTableViewCell else { fatalError() }
|
||||
cell.updateUI(for: notification)
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "followGroupCell", for: indexPath) as? FollowNotificationGroupTableViewCell else { fatalError() }
|
||||
cell.updateUI(group: group)
|
||||
cell.delegate = self
|
||||
return cell
|
||||
}
|
||||
|
@ -100,15 +113,17 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
|||
|
||||
|
||||
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
if indexPath.section == timelineSegments.count - 1,
|
||||
indexPath.row == timelineSegments[indexPath.section].count - 1 {
|
||||
if indexPath.row == groups.count - 1 {
|
||||
guard let older = older else { return }
|
||||
|
||||
let request = MastodonController.client.getNotifications(range: older)
|
||||
MastodonController.client.run(request) { result in
|
||||
guard case let .success(newNotifications, pagination) = result else { fatalError() }
|
||||
|
||||
self.timelineSegments[self.timelineSegments.count - 1].append(objects: newNotifications)
|
||||
let groups = NotificationGroup.createGroups(notifications: newNotifications, only: self.groupTypes)
|
||||
|
||||
self.groups.append(contentsOf: groups)
|
||||
|
||||
MastodonCache.addAll(notifications: newNotifications)
|
||||
MastodonCache.addAll(statuses: newNotifications.compactMap { $0.status })
|
||||
MastodonCache.addAll(accounts: newNotifications.map { $0.account })
|
||||
|
@ -137,7 +152,10 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
|||
MastodonController.client.run(request) { result in
|
||||
guard case let .success(newNotifications, pagination) = result else { fatalError() }
|
||||
|
||||
self.timelineSegments[0].insertAtBeginning(objects: newNotifications)
|
||||
let groups = NotificationGroup.createGroups(notifications: newNotifications, only: self.groupTypes)
|
||||
|
||||
self.groups.insert(contentsOf: groups, at: 0)
|
||||
|
||||
MastodonCache.addAll(notifications: newNotifications)
|
||||
MastodonCache.addAll(statuses: newNotifications.compactMap { $0.status })
|
||||
MastodonCache.addAll(accounts: newNotifications.map { $0.account })
|
||||
|
@ -160,17 +178,19 @@ extension NotificationsTableViewController: StatusTableViewCellDelegate {}
|
|||
extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
|
||||
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
let notificationID = timelineSegments[indexPath.section][indexPath.row]
|
||||
guard let notification = MastodonCache.notification(for: notificationID) else { continue }
|
||||
ImageCache.avatars.get(notification.account.avatar, completion: nil)
|
||||
for notificationID in groups[indexPath.row].notificationIDs {
|
||||
guard let notification = MastodonCache.notification(for: notificationID) else { continue }
|
||||
ImageCache.avatars.get(notification.account.avatar, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
let notificationID = timelineSegments[indexPath.section][indexPath.row]
|
||||
guard let notification = MastodonCache.notification(for: notificationID) else { continue }
|
||||
ImageCache.avatars.cancel(notification.account.url)
|
||||
for notificationID in groups[indexPath.row].notificationIDs {
|
||||
guard let notification = MastodonCache.notification(for: notificationID) else { continue }
|
||||
ImageCache.avatars.cancel(notification.account.avatar)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
//
|
||||
// ActionNotificationGroupTableViewCell.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 9/5/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import SwiftSoup
|
||||
|
||||
class ActionNotificationGroupTableViewCell: UITableViewCell {
|
||||
|
||||
var delegate: TuskerNavigationDelegate?
|
||||
|
||||
@IBOutlet weak var actionImageView: UIImageView!
|
||||
@IBOutlet weak var actionAvatarStackView: UIStackView!
|
||||
@IBOutlet weak var timestampLabel: UILabel!
|
||||
@IBOutlet weak var actionLabel: UILabel!
|
||||
@IBOutlet weak var statusContentLabel: UILabel!
|
||||
|
||||
var group: NotificationGroup!
|
||||
var statusID: String!
|
||||
|
||||
var authorAvatarURL: URL?
|
||||
var updateTimestampWorkItem: DispatchWorkItem?
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||
}
|
||||
|
||||
@objc func updateUIForPreferences() {
|
||||
let people = group.notificationIDs.compactMap(MastodonCache.notification(for:)).map { $0.account }
|
||||
updateActionLabel(people: people)
|
||||
|
||||
for case let imageView as UIImageView in actionAvatarStackView.arrangedSubviews {
|
||||
imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: imageView)
|
||||
}
|
||||
}
|
||||
|
||||
func updateUI(group: NotificationGroup) {
|
||||
guard group.kind == .favourite || group.kind == .reblog else {
|
||||
fatalError("Invalid notification type \(group.kind) for ActionNotificationGroupTableViewCell")
|
||||
}
|
||||
self.group = group
|
||||
|
||||
guard let firstNotification = MastodonCache.notification(for: group.notificationIDs.first!) else { fatalError() }
|
||||
let status = firstNotification.status!
|
||||
self.statusID = status.id
|
||||
|
||||
updateUIForPreferences()
|
||||
|
||||
switch group.kind {
|
||||
case .favourite:
|
||||
actionImageView.image = UIImage(systemName: "star.fill")
|
||||
case .reblog:
|
||||
actionImageView.image = UIImage(systemName: "repeat")
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
|
||||
let people = group.notificationIDs.compactMap(MastodonCache.notification(for:)).map { $0.account }
|
||||
|
||||
actionAvatarStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||
for account in people {
|
||||
let imageView = UIImageView()
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageView.layer.masksToBounds = true
|
||||
imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30
|
||||
ImageCache.avatars.get(account.avatar) { (data) in
|
||||
guard let data = data, self.group.id == group.id else { return }
|
||||
DispatchQueue.main.async {
|
||||
imageView.image = UIImage(data: data)
|
||||
}
|
||||
}
|
||||
actionAvatarStackView.addArrangedSubview(imageView)
|
||||
NSLayoutConstraint.activate([
|
||||
imageView.widthAnchor.constraint(equalToConstant: 30),
|
||||
imageView.heightAnchor.constraint(equalToConstant: 30)
|
||||
])
|
||||
}
|
||||
|
||||
updateTimestamp()
|
||||
|
||||
updateActionLabel(people: people)
|
||||
|
||||
let doc = try! SwiftSoup.parse(status.content)
|
||||
statusContentLabel.text = try! doc.text()
|
||||
}
|
||||
|
||||
func updateTimestamp() {
|
||||
guard let id = group.notificationIDs.first,
|
||||
let notification = MastodonCache.notification(for: id) else {
|
||||
fatalError("Missing cached status")
|
||||
}
|
||||
|
||||
timestampLabel.text = notification.createdAt.timeAgoString()
|
||||
|
||||
let delay: DispatchTimeInterval?
|
||||
switch notification.createdAt.timeAgo().1 {
|
||||
case .second:
|
||||
delay = .seconds(10)
|
||||
case .minute:
|
||||
delay = .seconds(60)
|
||||
default:
|
||||
delay = nil
|
||||
}
|
||||
if let delay = delay {
|
||||
updateTimestampWorkItem = DispatchWorkItem(block: self.updateTimestamp)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
||||
} else {
|
||||
updateTimestampWorkItem = nil
|
||||
}
|
||||
}
|
||||
|
||||
func updateActionLabel(people: [Account]) {
|
||||
let verb: String
|
||||
switch group.kind {
|
||||
case .favourite:
|
||||
verb = "Favorited"
|
||||
case .reblog:
|
||||
verb = "Reblogged"
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
let peopleStr: String
|
||||
// todo: figure out how to localize this
|
||||
switch people.count {
|
||||
case 1:
|
||||
peopleStr = people.first!.realDisplayName
|
||||
case 2:
|
||||
peopleStr = people.first!.realDisplayName + " and " + people.last!.realDisplayName
|
||||
default:
|
||||
peopleStr = people.dropLast().map { $0.realDisplayName }.joined(separator: ", ") + ", and " + people.last!.realDisplayName
|
||||
}
|
||||
actionLabel.text = "\(verb) by \(peopleStr)"
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14865.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14819.2"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="175" id="KGk-i7-Jjw" customClass="ActionNotificationGroupTableViewCell" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="175"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="175"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="hld-yu-Rmi">
|
||||
<rect key="frame" x="74" y="11" width="230" height="153"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="hTQ-P4-gOO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="230" height="30"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="b7l-YW-nQY">
|
||||
<rect key="frame" x="0.0" y="0.0" width="205.5" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="30" id="9uh-oo-JSM"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<view contentMode="scaleToFill" horizontalHuggingPriority="249" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5Ef-5g-b23">
|
||||
<rect key="frame" x="205.5" y="0.0" width="0.0" height="30"/>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="2m" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JN0-Bf-3qx">
|
||||
<rect key="frame" x="205.5" y="0.0" width="24.5" height="30"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="light" pointSize="17"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Actioned by Person 1, Person 2, and Person 3" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fkn-Gk-ngr">
|
||||
<rect key="frame" x="0.0" y="34" width="230" height="41"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="5" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lc7-zZ-HrZ">
|
||||
<rect key="frame" x="0.0" y="79" width="230" height="74"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="wUd-R6-gkG">
|
||||
<rect key="frame" x="36" y="11" width="30" height="30"/>
|
||||
<color key="tintColor" red="1" green="0.80000000000000004" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="30" id="Cx5-Jh-XEu"/>
|
||||
<constraint firstAttribute="height" constant="30" id="lWD-P5-gDr"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="hld-yu-Rmi" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="58" id="05d-IL-ZX0"/>
|
||||
<constraint firstItem="wUd-R6-gkG" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="Cg0-cz-htM"/>
|
||||
<constraint firstItem="hld-yu-Rmi" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="X0D-ZI-FXy"/>
|
||||
<constraint firstItem="hld-yu-Rmi" firstAttribute="leading" secondItem="wUd-R6-gkG" secondAttribute="trailing" constant="8" id="bby-eV-FDb"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="hld-yu-Rmi" secondAttribute="trailing" id="nC6-7Q-m0V"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="hld-yu-Rmi" secondAttribute="bottom" id="sB7-UM-p0X"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
|
||||
<connections>
|
||||
<outlet property="actionAvatarStackView" destination="b7l-YW-nQY" id="XW6-FM-tpc"/>
|
||||
<outlet property="actionImageView" destination="wUd-R6-gkG" id="HBp-p8-f3b"/>
|
||||
<outlet property="actionLabel" destination="fkn-Gk-ngr" id="bBG-a8-m5G"/>
|
||||
<outlet property="statusContentLabel" destination="lc7-zZ-HrZ" id="jgT-LU-rXt"/>
|
||||
<outlet property="timestampLabel" destination="JN0-Bf-3qx" id="Jlo-f6-DAi"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="-394.20289855072468" y="56.584821428571423"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
</document>
|
|
@ -1,207 +0,0 @@
|
|||
//
|
||||
// ActionNotificationTableViewCell.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 9/2/18.
|
||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class ActionNotificationTableViewCell: UITableViewCell {
|
||||
|
||||
var delegate: StatusTableViewCellDelegate? {
|
||||
didSet {
|
||||
contentLabel.navigationDelegate = delegate
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet weak var displayNameLabel: UILabel!
|
||||
@IBOutlet weak var usernameLabel: UILabel!
|
||||
@IBOutlet weak var contentLabel: StatusContentLabel!
|
||||
@IBOutlet weak var avatarContainerView: UIView!
|
||||
@IBOutlet weak var opAvatarImageView: UIImageView!
|
||||
@IBOutlet weak var actionAvatarImageView: UIImageView!
|
||||
@IBOutlet weak var actionLabel: UILabel!
|
||||
@IBOutlet weak var timestampLabel: UILabel!
|
||||
@IBOutlet weak var attachmentsView: UIStackView!
|
||||
|
||||
var notification: Pachyderm.Notification!
|
||||
var statusID: String!
|
||||
|
||||
var opAvatarURL: URL?
|
||||
var actionAvatarURL: URL?
|
||||
var updateTimestampWorkItem: DispatchWorkItem?
|
||||
|
||||
override func awakeFromNib() {
|
||||
displayNameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed)))
|
||||
displayNameLabel.isUserInteractionEnabled = true
|
||||
usernameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed)))
|
||||
usernameLabel.isUserInteractionEnabled = true
|
||||
opAvatarImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed)))
|
||||
opAvatarImageView.isUserInteractionEnabled = true
|
||||
opAvatarImageView.layer.masksToBounds = true
|
||||
actionAvatarImageView.layer.masksToBounds = true
|
||||
actionLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(actionPressed)))
|
||||
actionLabel.isUserInteractionEnabled = true
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||
}
|
||||
|
||||
@objc func updateUIForPreferences() {
|
||||
guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||
|
||||
opAvatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: opAvatarImageView)
|
||||
actionAvatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: actionAvatarImageView)
|
||||
displayNameLabel.text = status.account.realDisplayName
|
||||
|
||||
let verb: String
|
||||
switch notification.kind {
|
||||
case .favourite:
|
||||
verb = "Liked"
|
||||
case .reblog:
|
||||
verb = "Reblogged"
|
||||
default:
|
||||
fatalError("Invalid notification type \(notification.kind) for ActionNotificationTableViewCell")
|
||||
}
|
||||
actionLabel.text = "\(verb) by \(notification.account.realDisplayName)"
|
||||
}
|
||||
|
||||
func updateUI(for notification: Pachyderm.Notification) {
|
||||
guard notification.kind == .favourite || notification.kind == .reblog else {
|
||||
fatalError("Invalid notification type \(notification.kind) for ActionNotificationTableViewCell")
|
||||
}
|
||||
self.notification = notification
|
||||
let status = notification.status!
|
||||
self.statusID = status.id
|
||||
|
||||
updateUIForPreferences()
|
||||
|
||||
usernameLabel.text = "@\(status.account.acct)"
|
||||
opAvatarImageView.image = nil
|
||||
opAvatarURL = status.account.avatar
|
||||
ImageCache.avatars.get(status.account.avatar) { (data) in
|
||||
guard let data = data else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.opAvatarImageView.image = UIImage(data: data)
|
||||
self.opAvatarURL = nil
|
||||
}
|
||||
}
|
||||
actionAvatarImageView.image = nil
|
||||
actionAvatarURL = notification.account.avatar
|
||||
ImageCache.avatars.get(notification.account.avatar) { (data) in
|
||||
guard let data = data else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.actionAvatarImageView.image = UIImage(data: data)
|
||||
self.actionAvatarURL = nil
|
||||
}
|
||||
}
|
||||
updateTimestamp()
|
||||
let attachments = status.attachments.filter({ $0.kind == .image })
|
||||
if attachments.count > 0 {
|
||||
attachmentsView.isHidden = false
|
||||
for attachment in attachments {
|
||||
let url = attachment.textURL ?? attachment.url
|
||||
let label = UILabel()
|
||||
label.textColor = .secondaryLabel
|
||||
|
||||
let textAttachment = InlineTextAttachment()
|
||||
textAttachment.image = UIImage(named: "Link")!
|
||||
textAttachment.bounds = CGRect(x: 0, y: 0, width: label.font.pointSize, height: label.font.pointSize)
|
||||
textAttachment.fontDescender = label.font.descender
|
||||
let attachmentStr = NSAttributedString(attachment: textAttachment)
|
||||
let text = NSMutableAttributedString(string: " ")
|
||||
text.append(attachmentStr)
|
||||
text.append(NSAttributedString(string: " "))
|
||||
|
||||
text.append(NSAttributedString(string: "\(url.lastPathComponent)"))
|
||||
text.addAttribute(.foregroundColor, value: UIColor.systemBlue, range: NSRange(location: 0, length: 2))
|
||||
|
||||
label.attributedText = text
|
||||
attachmentsView.addArrangedSubview(label)
|
||||
}
|
||||
} else {
|
||||
attachmentsView.isHidden = true
|
||||
}
|
||||
|
||||
contentLabel.setTextFromHtml(status.content)
|
||||
}
|
||||
|
||||
func updateTimestamp() {
|
||||
guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||
|
||||
timestampLabel.text = status.createdAt.timeAgoString()
|
||||
let delay: DispatchTimeInterval?
|
||||
switch status.createdAt.timeAgo().1 {
|
||||
case .second:
|
||||
delay = .seconds(10)
|
||||
case .minute:
|
||||
delay = .seconds(60)
|
||||
default:
|
||||
delay = nil
|
||||
}
|
||||
if let delay = delay {
|
||||
updateTimestampWorkItem = DispatchWorkItem {
|
||||
self.updateTimestamp()
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
||||
} else {
|
||||
updateTimestampWorkItem = nil
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
if let url = opAvatarURL {
|
||||
ImageCache.avatars.cancel(url)
|
||||
}
|
||||
if let url = actionAvatarURL {
|
||||
ImageCache.avatars.cancel(url)
|
||||
}
|
||||
updateTimestampWorkItem?.cancel()
|
||||
updateTimestampWorkItem = nil
|
||||
attachmentsView.subviews.forEach { $0.removeFromSuperview() }
|
||||
}
|
||||
|
||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||
super.setSelected(selected, animated: animated)
|
||||
|
||||
if selected {
|
||||
delegate?.selected(status: statusID)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func accountPressed() {
|
||||
delegate?.selected(account: notification.status!.account.id)
|
||||
}
|
||||
|
||||
@objc func actionPressed() {
|
||||
delegate?.selected(account: notification.account.id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ActionNotificationTableViewCell: MenuPreviewProvider {
|
||||
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||
if avatarContainerView.frame.contains(location) {
|
||||
let accountID = notification.account.id
|
||||
return (content: { ProfileTableViewController(accountID: accountID) }, actions: { self.actionsForProfile(accountID: accountID) })
|
||||
} else if contentLabel.frame.contains(location),
|
||||
let link = contentLabel.getLink(atPoint: contentLabel.convert(location, from: self)) {
|
||||
return (
|
||||
content: { self.contentLabel.getViewController(forLink: link.url, inRange: link.range) },
|
||||
actions: {
|
||||
let text = (self.contentLabel.text! as NSString).substring(with: link.range)
|
||||
if let mention = self.contentLabel.getMention(for: link.url, text: text) {
|
||||
return self.actionsForProfile(accountID: mention.id)
|
||||
} else if let hashtag = self.contentLabel.getHashtag(for: link.url, text: text) {
|
||||
return self.actionsForHashtag(hashtag)
|
||||
} else {
|
||||
return self.actionsForURL(link.url)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
return (content: { ConversationTableViewController(for: self.statusID) }, actions: { self.actionsForStatus(statusID: self.statusID) })
|
||||
}
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14810.11" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14766.13"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
<capability name="iOS 13.0 system colors" minToolsVersion="11.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="pXp-xZ-SHj" customClass="ActionNotificationTableViewCell" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="150"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="ObS-PD-YeW">
|
||||
<rect key="frame" x="16" y="8" width="343" height="134"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Actioned by Person" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Cwu-6F-uNO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="148.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="secondaryLabelColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" verticalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="KYI-f9-4P1">
|
||||
<rect key="frame" x="0.0" y="28.5" width="343" height="105.5"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="RTx-MR-PMy">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="BE8-ts-R0p">
|
||||
<rect key="frame" x="0.0" y="0.0" width="40" height="40"/>
|
||||
<gestureRecognizers/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="40" id="UIH-BP-Nn9"/>
|
||||
<constraint firstAttribute="width" constant="40" id="Wr9-nX-NHl"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="bXi-tl-kR9">
|
||||
<rect key="frame" x="20" y="20" width="30" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="30" id="3Di-DR-TMt"/>
|
||||
<constraint firstAttribute="width" constant="30" id="aEX-cU-RO6"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="BE8-ts-R0p" firstAttribute="top" secondItem="RTx-MR-PMy" secondAttribute="top" id="DB6-au-fsh"/>
|
||||
<constraint firstItem="BE8-ts-R0p" firstAttribute="leading" secondItem="RTx-MR-PMy" secondAttribute="leading" id="G3O-JD-aUe"/>
|
||||
<constraint firstItem="bXi-tl-kR9" firstAttribute="top" secondItem="BE8-ts-R0p" secondAttribute="centerY" id="Lcz-S6-vlg"/>
|
||||
<constraint firstItem="bXi-tl-kR9" firstAttribute="leading" secondItem="BE8-ts-R0p" secondAttribute="centerX" id="OhH-h2-Ghz"/>
|
||||
<constraint firstAttribute="height" constant="50" id="PZT-yX-koc"/>
|
||||
<constraint firstAttribute="width" constant="50" id="pgY-SK-SfZ"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="OXS-YO-nMk">
|
||||
<rect key="frame" x="58" y="0.0" width="285" height="105.5"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="Q59-mb-dY9">
|
||||
<rect key="frame" x="0.0" y="0.0" width="285" height="20.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wFQ-nU-BGD">
|
||||
<rect key="frame" x="0.0" y="0.0" width="107" height="20.5"/>
|
||||
<gestureRecognizers/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="249" verticalHuggingPriority="251" horizontalCompressionResistancePriority="749" text="@username" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yXS-4q-Gje">
|
||||
<rect key="frame" x="115" y="0.0" width="137.5" height="20.5"/>
|
||||
<gestureRecognizers/>
|
||||
<fontDescription key="fontDescription" type="system" weight="light" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="secondaryLabelColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2m" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bLs-RB-2pT">
|
||||
<rect key="frame" x="260.5" y="0.0" width="24.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="light" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="secondaryLabelColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="30l-QK-uJH" customClass="StatusContentLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="28.5" width="285" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="HGa-49-qx0">
|
||||
<rect key="frame" x="0.0" y="57" width="285" height="48.5"/>
|
||||
</stackView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="OXS-YO-nMk" secondAttribute="bottom" id="9Bg-yr-1cl"/>
|
||||
<constraint firstItem="OXS-YO-nMk" firstAttribute="leading" secondItem="RTx-MR-PMy" secondAttribute="trailing" constant="8" id="BTr-cb-2wp"/>
|
||||
<constraint firstItem="OXS-YO-nMk" firstAttribute="top" secondItem="KYI-f9-4P1" secondAttribute="top" id="CxR-xr-he8"/>
|
||||
<constraint firstAttribute="trailing" secondItem="OXS-YO-nMk" secondAttribute="trailing" id="KKB-qH-F3R"/>
|
||||
<constraint firstItem="RTx-MR-PMy" firstAttribute="leading" secondItem="KYI-f9-4P1" secondAttribute="leading" id="rw0-sc-QH0"/>
|
||||
<constraint firstItem="RTx-MR-PMy" firstAttribute="top" secondItem="KYI-f9-4P1" secondAttribute="top" id="vOc-1D-br4"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="KYI-f9-4P1" firstAttribute="width" secondItem="ObS-PD-YeW" secondAttribute="width" id="nVW-9g-0T1"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="zEM-N9-Dkr" firstAttribute="bottom" secondItem="ObS-PD-YeW" secondAttribute="bottom" constant="8" id="98h-EH-lAh"/>
|
||||
<constraint firstItem="zEM-N9-Dkr" firstAttribute="trailing" secondItem="ObS-PD-YeW" secondAttribute="trailing" constant="16" id="ASg-kh-FIh"/>
|
||||
<constraint firstItem="ObS-PD-YeW" firstAttribute="top" secondItem="zEM-N9-Dkr" secondAttribute="top" constant="8" id="L5t-kw-TBz"/>
|
||||
<constraint firstItem="ObS-PD-YeW" firstAttribute="leading" secondItem="zEM-N9-Dkr" secondAttribute="leading" constant="16" id="zjz-OU-c06"/>
|
||||
</constraints>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<viewLayoutGuide key="safeArea" id="zEM-N9-Dkr"/>
|
||||
<connections>
|
||||
<outlet property="actionAvatarImageView" destination="bXi-tl-kR9" id="QB8-ll-vNb"/>
|
||||
<outlet property="actionLabel" destination="Cwu-6F-uNO" id="d7e-Za-Xed"/>
|
||||
<outlet property="attachmentsView" destination="HGa-49-qx0" id="x7p-uh-QRj"/>
|
||||
<outlet property="avatarContainerView" destination="RTx-MR-PMy" id="Qri-Cd-kjN"/>
|
||||
<outlet property="contentLabel" destination="30l-QK-uJH" id="eNc-Xt-C0E"/>
|
||||
<outlet property="displayNameLabel" destination="wFQ-nU-BGD" id="MkH-di-Bgr"/>
|
||||
<outlet property="opAvatarImageView" destination="BE8-ts-R0p" id="cu8-Kt-rbM"/>
|
||||
<outlet property="timestampLabel" destination="bLs-RB-2pT" id="qEh-Vp-oMK"/>
|
||||
<outlet property="usernameLabel" destination="yXS-4q-Gje" id="a0B-0O-0uv"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="29.600000000000001" y="38.680659670164921"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// FollowNotificationGroupTableViewCell.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 9/5/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class FollowNotificationGroupTableViewCell: UITableViewCell {
|
||||
|
||||
var delegate: TuskerNavigationDelegate?
|
||||
|
||||
@IBOutlet weak var avatarStackView: UIStackView!
|
||||
@IBOutlet weak var actionLabel: UILabel!
|
||||
|
||||
var group: NotificationGroup!
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||
}
|
||||
|
||||
@objc func updateUIForPreferences() {
|
||||
let people = group.notificationIDs.compactMap(MastodonCache.notification(for:)).map { $0.account }
|
||||
updateActionLabel(people: people)
|
||||
|
||||
for case let imageView as UIImageView in avatarStackView.arrangedSubviews {
|
||||
imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: imageView)
|
||||
}
|
||||
}
|
||||
|
||||
func updateUI(group: NotificationGroup) {
|
||||
self.group = group
|
||||
|
||||
let people = group.notificationIDs.compactMap(MastodonCache.notification(for:)).map { $0.account }
|
||||
|
||||
updateActionLabel(people: people)
|
||||
|
||||
avatarStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||
for account in people {
|
||||
let imageView = UIImageView()
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageView.layer.masksToBounds = true
|
||||
imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30
|
||||
ImageCache.avatars.get(account.avatar) { (data) in
|
||||
guard let data = data, self.group.id == group.id else { return }
|
||||
DispatchQueue.main.async {
|
||||
imageView.image = UIImage(data: data)
|
||||
}
|
||||
}
|
||||
avatarStackView.addArrangedSubview(imageView)
|
||||
NSLayoutConstraint.activate([
|
||||
imageView.widthAnchor.constraint(equalToConstant: 30),
|
||||
imageView.heightAnchor.constraint(equalToConstant: 30),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
func updateActionLabel(people: [Account]) {
|
||||
// todo: figure out how to localize this
|
||||
let peopleStr: String
|
||||
switch (people.count) {
|
||||
case 1:
|
||||
peopleStr = people.first!.realDisplayName
|
||||
case 2:
|
||||
peopleStr = people.first!.realDisplayName + " and " + people.last!.realDisplayName
|
||||
default:
|
||||
peopleStr = people.dropLast().map { $0.realDisplayName }.joined(separator: ", ") + ", and " + people.last!.realDisplayName
|
||||
|
||||
}
|
||||
actionLabel.text = "Followed by \(peopleStr)"
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14865.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14819.2"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="98" id="KGk-i7-Jjw" customClass="FollowNotificationGroupTableViewCell" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="98"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="98"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" translatesAutoresizingMaskIntoConstraints="NO" id="g8L-M7-dD6">
|
||||
<rect key="frame" x="74" y="11" width="230" height="76"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="xyB-aZ-YhR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="230" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="30" id="3ns-8D-P1Q"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Followed by Person 1 and Person 2" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bHA-9x-pcO">
|
||||
<rect key="frame" x="0.0" y="30" width="198" height="46"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="person.badge.plus.fill" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="7gy-KD-YT1">
|
||||
<rect key="frame" x="36" y="12.5" width="30" height="30.5"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="30" id="gvV-4g-2Xr"/>
|
||||
<constraint firstAttribute="height" constant="30" id="lS8-fq-ptY"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="7gy-KD-YT1" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="1Vb-q3-i8P"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="g8L-M7-dD6" secondAttribute="bottom" id="Dzg-eX-ZyM"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="g8L-M7-dD6" secondAttribute="trailing" id="Pg7-9Q-vYV"/>
|
||||
<constraint firstItem="g8L-M7-dD6" firstAttribute="leading" secondItem="7gy-KD-YT1" secondAttribute="trailing" constant="8" id="dCe-Ie-iRs"/>
|
||||
<constraint firstItem="g8L-M7-dD6" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="58" id="lWc-MX-lAl"/>
|
||||
<constraint firstItem="g8L-M7-dD6" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="xUY-IV-Jbu"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
|
||||
<connections>
|
||||
<outlet property="actionLabel" destination="bHA-9x-pcO" id="Woa-25-hgd"/>
|
||||
<outlet property="avatarStackView" destination="xyB-aZ-YhR" id="DDp-5c-Qdo"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="131.8840579710145" y="171.42857142857142"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="person.badge.plus.fill" catalog="system" width="64" height="58"/>
|
||||
</resources>
|
||||
</document>
|
|
@ -1,107 +0,0 @@
|
|||
//
|
||||
// FollowNotificationTableViewCell.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 9/2/18.
|
||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class FollowNotificationTableViewCell: UITableViewCell {
|
||||
|
||||
var delegate: StatusTableViewCellDelegate?
|
||||
|
||||
@IBOutlet weak var followLabel: UILabel!
|
||||
@IBOutlet weak var timestampLabel: UILabel!
|
||||
@IBOutlet weak var avatarImageView: UIImageView!
|
||||
@IBOutlet weak var displayNameLabel: UILabel!
|
||||
@IBOutlet weak var usernameLabel: UILabel!
|
||||
|
||||
var notification: Pachyderm.Notification!
|
||||
var accountID: String!
|
||||
|
||||
var avatarURL: URL?
|
||||
var updateTimestampWorkItem: DispatchWorkItem?
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
avatarImageView.layer.masksToBounds = true
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||
}
|
||||
|
||||
func updateUI(for notification: Pachyderm.Notification) {
|
||||
self.notification = notification
|
||||
let account = notification.account
|
||||
self.accountID = account.id
|
||||
|
||||
updateUIForPreferences()
|
||||
|
||||
usernameLabel.text = "@\(account.acct)"
|
||||
avatarImageView.image = nil
|
||||
avatarURL = account.avatar
|
||||
ImageCache.avatars.get(account.avatar) { (data) in
|
||||
guard let data = data else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = UIImage(data: data)
|
||||
self.avatarURL = nil
|
||||
}
|
||||
}
|
||||
updateTimestamp()
|
||||
}
|
||||
|
||||
@objc func updateUIForPreferences() {
|
||||
let account = MastodonCache.account(for: accountID)!
|
||||
|
||||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||
followLabel.text = "Followed by \(account.realDisplayName)"
|
||||
displayNameLabel.text = account.realDisplayName
|
||||
}
|
||||
|
||||
func updateTimestamp() {
|
||||
timestampLabel.text = notification.createdAt.timeAgoString()
|
||||
let delay: DispatchTimeInterval?
|
||||
switch notification.createdAt.timeAgo().1 {
|
||||
case .second:
|
||||
delay = .seconds(10)
|
||||
case .minute:
|
||||
delay = .seconds(60)
|
||||
default:
|
||||
delay = nil
|
||||
}
|
||||
if let delay = delay {
|
||||
updateTimestampWorkItem = DispatchWorkItem {
|
||||
self.updateTimestamp()
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
||||
} else {
|
||||
updateTimestampWorkItem = nil
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
if let url = avatarURL {
|
||||
ImageCache.avatars.cancel(url)
|
||||
}
|
||||
updateTimestampWorkItem?.cancel()
|
||||
updateTimestampWorkItem = nil
|
||||
}
|
||||
|
||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||
super.setSelected(selected, animated: animated)
|
||||
|
||||
if selected {
|
||||
delegate?.selected(account: accountID)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension FollowNotificationTableViewCell: MenuPreviewProvider {
|
||||
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||
return (content: { ProfileTableViewController(accountID: self.accountID) }, actions: { self.actionsForProfile(accountID: self.accountID) })
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14810.11" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14766.13"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
<capability name="iOS 13.0 system colors" minToolsVersion="11.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="FollowNotificationTableViewCell" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="95"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="h8B-F7-Ki1">
|
||||
<rect key="frame" x="16" y="8" width="343" height="79"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9Hp-m3-8ey">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="21"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="Followed by Person" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XXP-BS-J1N">
|
||||
<rect key="frame" x="0.0" y="0.0" width="318.5" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="secondaryLabelColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2m" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mKo-pt-Z5M">
|
||||
<rect key="frame" x="318.5" y="0.0" width="24.5" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="secondaryLabelColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="coX-E6-jv2">
|
||||
<rect key="frame" x="0.0" y="29" width="343" height="50"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Qop-Jw-jFp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="50" id="OgY-SB-V7y"/>
|
||||
<constraint firstAttribute="height" constant="50" id="tbZ-eK-yYq"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HM4-EZ-8FZ">
|
||||
<rect key="frame" x="58" y="0.0" width="107" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="@username" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lkz-Ko-ILm">
|
||||
<rect key="frame" x="58" y="28.5" width="91" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="light" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="secondaryLabelColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="HM4-EZ-8FZ" firstAttribute="top" secondItem="coX-E6-jv2" secondAttribute="top" id="8c8-GV-Fti"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Qop-Jw-jFp" secondAttribute="bottom" id="AA9-89-Sbh"/>
|
||||
<constraint firstItem="lkz-Ko-ILm" firstAttribute="leading" secondItem="Qop-Jw-jFp" secondAttribute="trailing" constant="8" id="a8d-2u-ipQ"/>
|
||||
<constraint firstItem="Qop-Jw-jFp" firstAttribute="leading" secondItem="coX-E6-jv2" secondAttribute="leading" id="gRD-du-B04"/>
|
||||
<constraint firstItem="lkz-Ko-ILm" firstAttribute="top" secondItem="HM4-EZ-8FZ" secondAttribute="bottom" constant="8" id="mx8-R6-9WE"/>
|
||||
<constraint firstItem="Qop-Jw-jFp" firstAttribute="top" secondItem="coX-E6-jv2" secondAttribute="top" id="nzs-jT-Ax2"/>
|
||||
<constraint firstItem="HM4-EZ-8FZ" firstAttribute="leading" secondItem="Qop-Jw-jFp" secondAttribute="trailing" constant="8" id="py1-om-Dsq"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="h8B-F7-Ki1" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="CoY-6b-cpE"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="h8B-F7-Ki1" secondAttribute="trailing" constant="16" id="Tvv-Eu-iFe"/>
|
||||
<constraint firstItem="h8B-F7-Ki1" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" id="h7Z-bn-SSY"/>
|
||||
<constraint firstAttribute="bottom" secondItem="h8B-F7-Ki1" secondAttribute="bottom" constant="8" id="vqk-hv-rxs"/>
|
||||
</constraints>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
||||
<connections>
|
||||
<outlet property="avatarImageView" destination="Qop-Jw-jFp" id="Sjo-KQ-qHx"/>
|
||||
<outlet property="displayNameLabel" destination="HM4-EZ-8FZ" id="V2G-a1-e2p"/>
|
||||
<outlet property="followLabel" destination="XXP-BS-J1N" id="266-fO-C0n"/>
|
||||
<outlet property="timestampLabel" destination="mKo-pt-Z5M" id="hxJ-0f-W0C"/>
|
||||
<outlet property="usernameLabel" destination="lkz-Ko-ILm" id="OPO-sE-Ay1"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="40.799999999999997" y="73.763118440779621"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
|
@ -1,11 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14810.12" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14865.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14766.15"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14819.2"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
<capability name="iOS 13.0 system colors" minToolsVersion="11.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
|
@ -20,7 +19,7 @@
|
|||
<label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Reblogged by Person" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lDH-50-AJZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="163.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="secondaryLabelColor"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" verticalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="ve3-Y1-NQH">
|
||||
|
@ -48,13 +47,13 @@
|
|||
<rect key="frame" x="115" y="0.0" width="137.5" height="20.5"/>
|
||||
<gestureRecognizers/>
|
||||
<fontDescription key="fontDescription" type="system" weight="light" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="secondaryLabelColor"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2m" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="35d-EA-ReR">
|
||||
<rect key="frame" x="260.5" y="0.0" width="24.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="light" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="secondaryLabelColor"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
|
@ -81,7 +80,7 @@
|
|||
</view>
|
||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="nbq-yr-2mA" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="108" width="343" height="0.0"/>
|
||||
<color key="backgroundColor" cocoaTouchSystemColor="secondarySystemBackgroundColor"/>
|
||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" priority="999" constant="200" id="J42-49-2MU"/>
|
||||
</constraints>
|
||||
|
@ -104,14 +103,14 @@
|
|||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6tW-z8-Qh9">
|
||||
<rect key="frame" x="215.5" y="0.0" width="23" height="22"/>
|
||||
<rect key="frame" x="215.5" y="0.0" width="22.5" height="22"/>
|
||||
<state key="normal" image="repeat" catalog="system"/>
|
||||
<connections>
|
||||
<action selector="reblogPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="JQI-VT-wTt"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="982-J4-NGl">
|
||||
<rect key="frame" x="324.5" y="0.0" width="18.5" height="22"/>
|
||||
<rect key="frame" x="324" y="0.0" width="19" height="22"/>
|
||||
<state key="normal" image="ellipsis" catalog="system"/>
|
||||
<connections>
|
||||
<action selector="morePressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="dcV-Ez-EIe"/>
|
||||
|
@ -129,7 +128,7 @@
|
|||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="yNh-ac-v6c" secondAttribute="trailing" constant="16" id="2qQ-80-4Ui"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="yNh-ac-v6c" secondAttribute="trailing" id="2qQ-80-4Ui"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="yNh-ac-v6c" secondAttribute="bottom" constant="8" id="RKQ-BR-mlH"/>
|
||||
<constraint firstItem="yNh-ac-v6c" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="YVV-xZ-N4f"/>
|
||||
<constraint firstItem="yNh-ac-v6c" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" id="aAG-d7-dmV"/>
|
||||
|
|
Loading…
Reference in New Issue