Add notifications screen

This commit is contained in:
Shadowfacts 2018-09-03 16:54:03 -04:00
parent 889151dcc2
commit db54922040
Signed by untrusted user: shadowfacts
GPG Key ID: 94A5AB95422746E5
19 changed files with 1051 additions and 27 deletions

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg4592"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
sodipodi:docname="Download.svg">
<defs
id="defs4586" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.979899"
inkscape:cx="166.13768"
inkscape:cy="136.01503"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="2560"
inkscape:window-height="1395"
inkscape:window-x="1920"
inkscape:window-y="1"
inkscape:window-maximized="1" />
<metadata
id="metadata4589">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.7641871;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect5166"
width="100"
height="10"
x="-45"
y="302"
ry="4.9918189" />
<rect
transform="matrix(-0.70710679,-0.70710677,-0.70710679,0.70710677,0,0)"
ry="4.4665961"
y="199.22142"
x="-214.47757"
height="8.9331923"
width="66.99894"
id="rect5164"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.46821165;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
transform="matrix(0,1,1,0,0,0)"
ry="4.4665961"
y="0"
x="212"
height="8.9331923"
width="85"
id="rect5168"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.78008413;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.46821189;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect5162"
width="66.99894"
height="8.9331923"
x="141.1559"
y="205.54411"
ry="4.4665961"
transform="matrix(-0.70710679,0.70710677,0.70710679,0.70710677,0,0)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

78
Artwork/Icons/Link.svg Normal file
View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg5214"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
sodipodi:docname="Link.svg">
<defs
id="defs5208" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8"
inkscape:cx="166.9267"
inkscape:cy="23.876042"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="2560"
inkscape:window-height="1395"
inkscape:window-x="1920"
inkscape:window-y="1"
inkscape:window-maximized="1" />
<metadata
id="metadata5211">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<ellipse
cy="308.6665"
cx="68.333336"
id="circle5763"
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:9;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
rx="24.99999"
ry="25.000156" />
<circle
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:9;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path5218"
cx="28.333334"
cy="268.66666"
r="25" />
<rect
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:3.61034346;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect5767"
width="65"
height="12.5"
x="206.42897"
y="164.74048"
ry="6.25"
transform="rotate(45)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -13,6 +13,13 @@
D6333B372137838300CE884A /* AttributedString+Trim.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Trim.swift */; };
D6333B772138D94E00CE884A /* ComposeMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B762138D94E00CE884A /* ComposeMediaView.swift */; };
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; };
D641C771213CA9EC004B4513 /* Notifications.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D641C770213CA9EC004B4513 /* Notifications.storyboard */; };
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 */; };
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */; };
D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */; };
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */; };
@ -100,6 +107,13 @@
D6333B362137838300CE884A /* AttributedString+Trim.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Trim.swift"; sourceTree = "<group>"; };
D6333B762138D94E00CE884A /* ComposeMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeMediaView.swift; sourceTree = "<group>"; };
D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = "<group>"; };
D641C770213CA9EC004B4513 /* Notifications.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Notifications.storyboard; 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>"; };
D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageExpandAnimationController.swift; sourceTree = "<group>"; };
D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageShrinkAnimationController.swift; sourceTree = "<group>"; };
D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageInteractionController.swift; sourceTree = "<group>"; };
@ -227,6 +241,11 @@
D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */,
D6333B762138D94E00CE884A /* ComposeMediaView.swift */,
D6C94D882139E6EC00CB5196 /* AttachmentView.swift */,
D641C778213CAC56004B4513 /* ActionNotificationTableViewCell.xib */,
D641C776213CAA9E004B4513 /* ActionNotificationTableViewCell.swift */,
D641C77A213CB017004B4513 /* FollowNotificationTableViewCell.xib */,
D641C77C213CB024004B4513 /* FollowNotificationTableViewCell.swift */,
D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */,
);
path = Views;
sourceTree = "<group>";
@ -304,6 +323,7 @@
D663626721360E2C00C9CBA2 /* PreferencesTableViewController.swift */,
D66362702136338600C9CBA2 /* ComposeViewController.swift */,
D6C94D862139E62700CB5196 /* LargeImageViewController.swift */,
D641C772213CAA25004B4513 /* NotificationsTableViewController.swift */,
);
path = "View Controllers";
sourceTree = "<group>";
@ -319,6 +339,7 @@
D663626521360DD700C9CBA2 /* Preferences.storyboard */,
D663626E213632A000C9CBA2 /* Compose.storyboard */,
D6C94D842139DFD800CB5196 /* LargeImage.storyboard */,
D641C770213CA9EC004B4513 /* Notifications.storyboard */,
);
path = Storyboards;
sourceTree = "<group>";
@ -436,6 +457,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D641C779213CAC56004B4513 /* ActionNotificationTableViewCell.xib in Resources */,
D667E5F32135BC260057A976 /* Conversation.storyboard in Resources */,
D667E5E921349EE50057A976 /* ProfileHeaderTableViewCell.xib in Resources */,
D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */,
@ -444,9 +466,11 @@
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */,
D6F953EE21251A0700CF0F2B /* Timeline.storyboard in Resources */,
D663626F213632A000C9CBA2 /* Compose.storyboard in Resources */,
D641C77B213CB017004B4513 /* FollowNotificationTableViewCell.xib in Resources */,
D6C94D852139DFD800CB5196 /* LargeImage.storyboard in Resources */,
D6D4DDD5212518A000E1C4BB /* Main.storyboard in Resources */,
D667E5E3213499F70057A976 /* Profile.storyboard in Resources */,
D641C771213CA9EC004B4513 /* Notifications.storyboard in Resources */,
D663626621360DD700C9CBA2 /* Preferences.storyboard in Resources */,
D667E5E12134937B0057A976 /* StatusTableViewCell.xib in Resources */,
);
@ -490,9 +514,11 @@
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */,
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */,
D641C77F213DC78A004B4513 /* InlineTextAttachment.swift in Sources */,
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */,
D6333B372137838300CE884A /* AttributedString+Trim.swift in Sources */,
D667E5EF2134C39F0057A976 /* StatusContentLabel.swift in Sources */,
D641C777213CAA9E004B4513 /* ActionNotificationTableViewCell.swift in Sources */,
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */,
D663626821360E2C00C9CBA2 /* PreferencesTableViewController.swift in Sources */,
D66362732136FFC600C9CBA2 /* UITextView+Placeholder.swift in Sources */,
@ -501,6 +527,8 @@
D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */,
D663626A2136163000C9CBA2 /* PreferencesAdaptive.swift in Sources */,
D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */,
D641C77D213CB024004B4513 /* FollowNotificationTableViewCell.swift in Sources */,
D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */,
D64A0CD32132153900640E3B /* HTMLContentLabel.swift in Sources */,
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */,
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */,

View File

@ -0,0 +1,25 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "Link.pdf",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template",
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,70 @@
%PDF-1.5
%µí®û
3 0 obj
<< /Length 4 0 R
/Filter /FlateDecode
>>
stream
xœ}RËN1 ¼û+ü˜Ø›‡ýHH
BôP8ðû8ízÛnZ­œqfœqœ$ßý ^?'|û)T˜|ïÖÿx|ÂD _ ãî<>÷«DU1Ëû¤6ÒV<C392>­QJŠ[ŒŒL•8ŸÄ3pG.cÅp¤È<C2A4>ð" Ò~Ì imQ"ò°PƒØ/Š-‡Í%Öþ:¾ÿÑÝnàŸëà)«¢NždçGE¨U9ÆsbÀŽy,2œ©•Sýβø ŠpF€}]$ŽÕ¹¥ÑÿE;£ýÃóùzC3šÌNcô);¯ UÒ2á'
3•ZQ<EFBFBD>ÌŸWÀ¦Ô& :`cj¶7è÷\ÝJÍ^À§ªÕ_¦,°yXØ-Óä¶ÏFÔOŸÖä;ìv­a +>ï:³ÃlGeuOÉ\žÑ„²{ž!¬›·qÑï+làuÛ¥>
endstream
endobj
4 0 obj
330
endobj
2 0 obj
<<
/ExtGState <<
/a0 << /CA 1 /ca 1 >>
>>
>>
endobj
5 0 obj
<< /Type /Page
/Parent 1 0 R
/MediaBox [ 0 0 280.629913 280.629944 ]
/Contents 3 0 R
/Group <<
/Type /Group
/S /Transparency
/I true
/CS /DeviceRGB
>>
/Resources 2 0 R
>>
endobj
1 0 obj
<< /Type /Pages
/Kids [ 5 0 R ]
/Count 1
>>
endobj
6 0 obj
<< /Creator (cairo 1.14.8 (http://cairographics.org))
/Producer (cairo 1.14.8 (http://cairographics.org))
>>
endobj
7 0 obj
<< /Type /Catalog
/Pages 1 0 R
>>
endobj
xref
0 8
0000000000 65535 f
0000000744 00000 n
0000000444 00000 n
0000000015 00000 n
0000000422 00000 n
0000000516 00000 n
0000000809 00000 n
0000000936 00000 n
trailer
<< /Size 8
/Root 7 0 R
/Info 6 0 R
>>
startxref
988
%%EOF

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.13.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EzH-QO-2dB">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.9"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Notifications-->
<scene sceneID="vSO-cl-PiH">
<objects>
<tableViewController id="E5I-Er-vAb" customClass="NotificationsTableViewController" customModule="Tusker" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="0DS-T6-PUs">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<outlet property="dataSource" destination="E5I-Er-vAb" id="w6K-Pf-fhT"/>
<outlet property="delegate" destination="E5I-Er-vAb" id="O7J-GD-Bmh"/>
</connections>
</tableView>
<navigationItem key="navigationItem" title="Notifications" id="wg3-0i-z0p"/>
<refreshControl key="refreshControl" opaque="NO" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" id="Mmb-i6-RhA">
<autoresizingMask key="autoresizingMask"/>
<connections>
<action selector="refreshNotifications:" destination="E5I-Er-vAb" eventType="primaryActionTriggered" id="Old-Un-zAH"/>
</connections>
</refreshControl>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="rl4-aD-Trs" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="390" y="139"/>
</scene>
<!--Notifications-->
<scene sceneID="4AI-j7-p9U">
<objects>
<navigationController id="EzH-QO-2dB" sceneMemberID="viewController">
<tabBarItem key="tabBarItem" title="Notifications" id="rKf-9n-3CY"/>
<simulatedTabBarMetrics key="simulatedBottomBarMetrics"/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="zS8-NX-vjx">
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
<segue destination="E5I-Er-vAb" kind="relationship" relationship="rootViewController" id="udi-gd-H1N"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="2A9-Wz-JHf" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-562" y="139"/>
</scene>
</scenes>
</document>

View File

@ -33,7 +33,7 @@
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
<autoresizingMask key="autoresizingMask"/>
<connections>
<action selector="refreshStatuses:" destination="6nb-nb-cMm" eventType="valueChanged" id="ndM-Yv-ISu"/>
<action selector="refreshStatuses:" destination="6nb-nb-cMm" eventType="primaryActionTriggered" id="XJM-UU-ZkT"/>
</connections>
</refreshControl>
</tableViewController>

View File

@ -114,11 +114,9 @@ class ConversationViewController: UIViewController, UITableViewDataSource, UITab
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
let status = statuses[indexPath.row]
guard status != mainStatus else { return }
guard let cell = tableView.cellForRow(at: indexPath) as? StatusTableViewCell else { fatalError() }
cell.didSelect()
return status == mainStatus ? nil : indexPath
}
}

View File

@ -17,6 +17,7 @@ class MainTabBarViewController: UITabBarController {
TimelineTableViewController.create(for: .home),
TimelineTableViewController.create(for: .federated),
TimelineTableViewController.create(for: .local),
NotificationsTableViewController.create(),
PreferencesTableViewController.create()
]
}

View File

@ -0,0 +1,140 @@
//
// NotificationsTableViewController.swift
// Tusker
//
// Created by Shadowfacts on 9/2/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import UIKit
import MastodonKit
class NotificationsTableViewController: UITableViewController {
static func create() -> UIViewController {
guard let navigationController = UIStoryboard(name: "Notifications", bundle: nil).instantiateInitialViewController() as? UINavigationController else { fatalError() }
return navigationController
}
var notifications: [MastodonKit.Notification] = [] {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
var newer: RequestRange?
var older: RequestRange?
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem
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")
let req = Notifications.all()
MastodonController.shared.client.run(req) { result in
guard case let .success(notifications, pagination) = result else { fatalError() }
self.notifications = notifications
self.newer = pagination?.previous
self.older = pagination?.next
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
for cell in tableView.visibleCells {
if let cell = cell as? PreferencesAdaptive {
cell.updateUIForPreferences()
}
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return notifications.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let notification = notifications[indexPath.row]
switch notification.type {
case .mention:
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() }
let status = notification.status!
cell.updateUI(for: status)
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)
cell.delegate = self
return cell
case .follow:
guard let cell = tableView.dequeueReusableCell(withIdentifier: "followCell", for: indexPath) as? FollowNotificationTableViewCell else { fatalError() }
cell.updateUI(for: notification)
cell.delegate = self
return cell
}
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == notifications.count - 1 {
guard let older = older else { return }
let req = Notifications.all(range: older)
MastodonController.shared.client.run(req) { result in
guard case let .success(newNotifications, pagination) = result else { fatalError() }
self.older = pagination?.next
self.notifications.append(contentsOf: newNotifications)
}
}
}
@IBAction func refreshNotifications(_ sender: Any) {
guard let newer = newer else { return }
let req = Notifications.all(range: newer)
MastodonController.shared.client.run(req) { result in
guard case let .success(newNotifications, pagination) = result else { fatalError() }
self.newer = pagination?.previous
self.notifications.insert(contentsOf: newNotifications, at: 0)
DispatchQueue.main.async {
self.refreshControl?.endRefreshing()
// maintain the current position in the list (don't scroll to top)
self.tableView.scrollToRow(at: IndexPath(row: newNotifications.count, section: 0), at: .top, animated: false)
}
}
}
}

View File

@ -123,12 +123,6 @@ class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard indexPath.section == 1 else { return }
guard let cell = tableView.cellForRow(at: indexPath) as? StatusTableViewCell else { fatalError() }
cell.didSelect()
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.section == 1 && indexPath.row == statuses.count - 1 {
guard let older = older else { return }

View File

@ -103,11 +103,6 @@ class TimelineTableViewController: UITableViewController {
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? StatusTableViewCell else { fatalError() }
cell.didSelect()
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == statuses.count - 1 {
guard let older = older else { return }

View File

@ -0,0 +1,201 @@
//
// ActionNotificationTableViewCell.swift
// Tusker
//
// Created by Shadowfacts on 9/2/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import UIKit
import MastodonKit
class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
var delegate: StatusTableViewCellDelegate?
@IBOutlet weak var displayNameLabel: UILabel!
@IBOutlet weak var usernameLabel: UILabel!
@IBOutlet weak var contentLabel: StatusContentLabel!
@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: MastodonKit.Notification!
var status: Status!
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
contentLabel.delegate = self
}
func updateUIForPreferences() {
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.type {
case .favourite:
verb = "Liked"
case .reblog:
verb = "Reblogged"
default:
fatalError("Invalid notification type \(notification.type) for ActionNotificationTableViewCell")
}
actionLabel.text = "\(verb) by \(notification.account.realDisplayName)"
}
func updateUI(for notification: MastodonKit.Notification) {
guard notification.type == .favourite || notification.type == .reblog else {
fatalError("Invalid notification type \(notification.type) for ActionNotificationTableViewCell")
}
self.notification = notification
self.status = notification.status!
updateUIForPreferences()
usernameLabel.text = "@\(status.account.acct)"
opAvatarImageView.image = nil
if let url = URL(string: status.account.avatar) {
opAvatarURL = url
AvatarCache.shared.get(url) { image in
DispatchQueue.main.async {
self.opAvatarImageView.image = image
self.opAvatarURL = nil
}
}
}
actionAvatarImageView.image = nil
if let url = URL(string: notification.account.avatar) {
actionAvatarURL = url
AvatarCache.shared.get(url) { image in
DispatchQueue.main.async {
self.actionAvatarImageView.image = image
self.actionAvatarURL = nil
}
}
}
updateTimestamp()
let attachments = status.mediaAttachments.filter({ $0.type == .image })
if attachments.count > 0 {
attachmentsView.isHidden = false
for attachment in attachments {
guard let url = URL(string: attachment.textURL ?? attachment.url) else { continue }
let label = UILabel()
label.textColor = .darkGray
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.addAttribute(.font, value: UIFont.systemFont(ofSize: 0), range: NSRange(location: 0, length: text.length))
text.append(NSAttributedString(string: "\(url.lastPathComponent)"))
text.addAttribute(.foregroundColor, value: UIColor.red, range: NSRange(location: 0, length: 2))
// let text = NSMutableAttributedString(string: " \(url.lastPathComponent)")
// let imageAttachment = InlineTextAttachment()
// imageAttachment.image = UIImage(named: "Link")!
// imageAttachment.bounds = CGRect(x: 0, y: 0, width: label.font.pointSize, height: label.font.pointSize)
// imageAttachment.fontDescender = label.font.descender
// let imageStr = NSMutableAttributedString(attachment: imageAttachment)
// imageStr.setAttributes([.foregroundColor: UIColor.darkGray], range: )
// text.insert(imageStr, at: 0)
label.attributedText = text
attachmentsView.addArrangedSubview(label)
}
} else {
attachmentsView.isHidden = true
}
contentLabel.status = status
}
func updateTimestamp() {
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 {
AvatarCache.shared.cancel(url)
}
if let url = actionAvatarURL {
AvatarCache.shared.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: status)
}
}
@objc func accountPressed() {
delegate?.selected(account: status.account)
}
@objc func actionPressed() {
delegate?.selected(account: notification.account)
}
}
extension ActionNotificationTableViewCell: HTMLContentLabelDelegate {
func selected(mention: Mention) {
delegate?.selected(mention: mention)
}
func selected(tag: Tag) {
delegate?.selected(tag: tag)
}
func selected(url: URL) {
delegate?.selected(url: url)
}
}

View File

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14313.13.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.9"/>
<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"/>
<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" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<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>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<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="102.5" height="20.5"/>
<gestureRecognizers/>
<fontDescription key="fontDescription" type="system" 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="110.5" y="0.0" width="142" height="20.5"/>
<gestureRecognizers/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<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" pointSize="17"/>
<color key="textColor" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<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" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<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="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>

View File

@ -0,0 +1,97 @@
//
// FollowNotificationTableViewCell.swift
// Tusker
//
// Created by Shadowfacts on 9/2/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import UIKit
import MastodonKit
class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
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: MastodonKit.Notification!
var account: Account!
var avatarURL: URL?
var updateTimestampWorkItem: DispatchWorkItem?
override func awakeFromNib() {
super.awakeFromNib()
avatarImageView.layer.masksToBounds = true
}
func updateUIForPreferences() {
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
followLabel.text = "Followed by \(account.realDisplayName)"
displayNameLabel.text = account.realDisplayName
}
func updateUI(for notification: MastodonKit.Notification) {
self.notification = notification
self.account = notification.account
updateUIForPreferences()
usernameLabel.text = "@\(account.acct)"
avatarImageView.image = nil
if let url = URL(string: account.avatar) {
avatarURL = url
AvatarCache.shared.get(url) { image in
DispatchQueue.main.async {
self.avatarImageView.image = image
self.avatarURL = nil
}
}
}
updateTimestamp()
}
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 {
AvatarCache.shared.cancel(url)
}
updateTimestampWorkItem?.cancel()
updateTimestampWorkItem = nil
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
delegate?.selected(account: account)
}
}
}

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14313.13.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.9"/>
<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"/>
<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" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<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" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<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="102.5" height="20.5"/>
<fontDescription key="fontDescription" type="system" 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.5" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<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" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<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>

View File

@ -0,0 +1,21 @@
//
// InlineTextAttachment.swift
// Tusker
//
// Created by Shadowfacts on 9/3/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import UIKit
class InlineTextAttachment: NSTextAttachment {
var fontDescender: CGFloat!
override func attachmentBounds(for textContainer: NSTextContainer?, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect {
var rect = super.attachmentBounds(for: textContainer, proposedLineFragment: lineFrag, glyphPosition: position, characterIndex: charIndex)
rect.origin.y = fontDescender
return rect
}
}

View File

@ -66,9 +66,7 @@ class ProfileHeaderTableViewCell: UITableViewCell, PreferencesAdaptive {
}
if let url = URL(string: account.header) {
headerImageDownloadTask = URLSession.shared.dataTask(with: url) { data, response, error in
guard error == nil,
let data = data,
let image = UIImage(data: data) else { return }
guard error == nil, let data = data, let image = UIImage(data: data) else { return }
DispatchQueue.main.async {
self.headerImageView.image = image
self.headerImageDownloadTask = nil

View File

@ -44,9 +44,7 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
var reblogger: Account?
var avatarURL: URL?
var updateTimestampWorkItem: DispatchWorkItem?
var attachmentDataTasks: [URLSessionDataTask] = []
override func awakeFromNib() {
@ -61,6 +59,7 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
avatarImageView.layer.masksToBounds = true
attachmentsView.layer.cornerRadius = 5
attachmentsView.layer.masksToBounds = true
contentLabel.delegate = self
}
func updateUIForPreferences() {
@ -102,7 +101,6 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
}
updateTimestamp()
attachmentsView.subviews.forEach { $0.removeFromSuperview() }
let attachments = status.mediaAttachments.filter({ $0.type == .image })
if attachments.count > 0 {
attachmentsView.isHidden = false
@ -132,7 +130,6 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
}
contentLabel.status = status
contentLabel.delegate = self
}
func updateTimestamp() {
@ -170,6 +167,15 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
updateTimestampWorkItem = nil
attachmentsView.subviews.forEach { view in
(view as? AttachmentView)?.task?.cancel()
view.removeFromSuperview()
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
delegate?.selected(status: status)
}
}
@ -186,10 +192,6 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
delegate?.selected(account: reblogger)
}
func didSelect() {
delegate?.selected(status: status)
}
}
extension StatusTableViewCell: HTMLContentLabelDelegate {