Add notifications screen
This commit is contained in:
parent
fe576ba028
commit
79f4e2a556
|
@ -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 |
|
@ -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 |
|
@ -13,6 +13,13 @@
|
||||||
D6333B372137838300CE884A /* AttributedString+Trim.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Trim.swift */; };
|
D6333B372137838300CE884A /* AttributedString+Trim.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Trim.swift */; };
|
||||||
D6333B772138D94E00CE884A /* ComposeMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B762138D94E00CE884A /* ComposeMediaView.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 */; };
|
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 */; };
|
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */; };
|
||||||
D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */; };
|
D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */; };
|
||||||
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C959213B5D0500269FB5 /* LargeImageInteractionController.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageInteractionController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -227,6 +241,11 @@
|
||||||
D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */,
|
D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */,
|
||||||
D6333B762138D94E00CE884A /* ComposeMediaView.swift */,
|
D6333B762138D94E00CE884A /* ComposeMediaView.swift */,
|
||||||
D6C94D882139E6EC00CB5196 /* AttachmentView.swift */,
|
D6C94D882139E6EC00CB5196 /* AttachmentView.swift */,
|
||||||
|
D641C778213CAC56004B4513 /* ActionNotificationTableViewCell.xib */,
|
||||||
|
D641C776213CAA9E004B4513 /* ActionNotificationTableViewCell.swift */,
|
||||||
|
D641C77A213CB017004B4513 /* FollowNotificationTableViewCell.xib */,
|
||||||
|
D641C77C213CB024004B4513 /* FollowNotificationTableViewCell.swift */,
|
||||||
|
D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -304,6 +323,7 @@
|
||||||
D663626721360E2C00C9CBA2 /* PreferencesTableViewController.swift */,
|
D663626721360E2C00C9CBA2 /* PreferencesTableViewController.swift */,
|
||||||
D66362702136338600C9CBA2 /* ComposeViewController.swift */,
|
D66362702136338600C9CBA2 /* ComposeViewController.swift */,
|
||||||
D6C94D862139E62700CB5196 /* LargeImageViewController.swift */,
|
D6C94D862139E62700CB5196 /* LargeImageViewController.swift */,
|
||||||
|
D641C772213CAA25004B4513 /* NotificationsTableViewController.swift */,
|
||||||
);
|
);
|
||||||
path = "View Controllers";
|
path = "View Controllers";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -319,6 +339,7 @@
|
||||||
D663626521360DD700C9CBA2 /* Preferences.storyboard */,
|
D663626521360DD700C9CBA2 /* Preferences.storyboard */,
|
||||||
D663626E213632A000C9CBA2 /* Compose.storyboard */,
|
D663626E213632A000C9CBA2 /* Compose.storyboard */,
|
||||||
D6C94D842139DFD800CB5196 /* LargeImage.storyboard */,
|
D6C94D842139DFD800CB5196 /* LargeImage.storyboard */,
|
||||||
|
D641C770213CA9EC004B4513 /* Notifications.storyboard */,
|
||||||
);
|
);
|
||||||
path = Storyboards;
|
path = Storyboards;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -436,6 +457,7 @@
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
D641C779213CAC56004B4513 /* ActionNotificationTableViewCell.xib in Resources */,
|
||||||
D667E5F32135BC260057A976 /* Conversation.storyboard in Resources */,
|
D667E5F32135BC260057A976 /* Conversation.storyboard in Resources */,
|
||||||
D667E5E921349EE50057A976 /* ProfileHeaderTableViewCell.xib in Resources */,
|
D667E5E921349EE50057A976 /* ProfileHeaderTableViewCell.xib in Resources */,
|
||||||
D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */,
|
D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */,
|
||||||
|
@ -444,9 +466,11 @@
|
||||||
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */,
|
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */,
|
||||||
D6F953EE21251A0700CF0F2B /* Timeline.storyboard in Resources */,
|
D6F953EE21251A0700CF0F2B /* Timeline.storyboard in Resources */,
|
||||||
D663626F213632A000C9CBA2 /* Compose.storyboard in Resources */,
|
D663626F213632A000C9CBA2 /* Compose.storyboard in Resources */,
|
||||||
|
D641C77B213CB017004B4513 /* FollowNotificationTableViewCell.xib in Resources */,
|
||||||
D6C94D852139DFD800CB5196 /* LargeImage.storyboard in Resources */,
|
D6C94D852139DFD800CB5196 /* LargeImage.storyboard in Resources */,
|
||||||
D6D4DDD5212518A000E1C4BB /* Main.storyboard in Resources */,
|
D6D4DDD5212518A000E1C4BB /* Main.storyboard in Resources */,
|
||||||
D667E5E3213499F70057A976 /* Profile.storyboard in Resources */,
|
D667E5E3213499F70057A976 /* Profile.storyboard in Resources */,
|
||||||
|
D641C771213CA9EC004B4513 /* Notifications.storyboard in Resources */,
|
||||||
D663626621360DD700C9CBA2 /* Preferences.storyboard in Resources */,
|
D663626621360DD700C9CBA2 /* Preferences.storyboard in Resources */,
|
||||||
D667E5E12134937B0057A976 /* StatusTableViewCell.xib in Resources */,
|
D667E5E12134937B0057A976 /* StatusTableViewCell.xib in Resources */,
|
||||||
);
|
);
|
||||||
|
@ -490,9 +514,11 @@
|
||||||
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
|
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
|
||||||
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */,
|
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */,
|
||||||
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */,
|
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */,
|
||||||
|
D641C77F213DC78A004B4513 /* InlineTextAttachment.swift in Sources */,
|
||||||
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */,
|
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */,
|
||||||
D6333B372137838300CE884A /* AttributedString+Trim.swift in Sources */,
|
D6333B372137838300CE884A /* AttributedString+Trim.swift in Sources */,
|
||||||
D667E5EF2134C39F0057A976 /* StatusContentLabel.swift in Sources */,
|
D667E5EF2134C39F0057A976 /* StatusContentLabel.swift in Sources */,
|
||||||
|
D641C777213CAA9E004B4513 /* ActionNotificationTableViewCell.swift in Sources */,
|
||||||
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */,
|
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */,
|
||||||
D663626821360E2C00C9CBA2 /* PreferencesTableViewController.swift in Sources */,
|
D663626821360E2C00C9CBA2 /* PreferencesTableViewController.swift in Sources */,
|
||||||
D66362732136FFC600C9CBA2 /* UITextView+Placeholder.swift in Sources */,
|
D66362732136FFC600C9CBA2 /* UITextView+Placeholder.swift in Sources */,
|
||||||
|
@ -501,6 +527,8 @@
|
||||||
D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */,
|
D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */,
|
||||||
D663626A2136163000C9CBA2 /* PreferencesAdaptive.swift in Sources */,
|
D663626A2136163000C9CBA2 /* PreferencesAdaptive.swift in Sources */,
|
||||||
D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */,
|
D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */,
|
||||||
|
D641C77D213CB024004B4513 /* FollowNotificationTableViewCell.swift in Sources */,
|
||||||
|
D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */,
|
||||||
D64A0CD32132153900640E3B /* HTMLContentLabel.swift in Sources */,
|
D64A0CD32132153900640E3B /* HTMLContentLabel.swift in Sources */,
|
||||||
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */,
|
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */,
|
||||||
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */,
|
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */,
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
%PDF-1.5
|
||||||
|
%µí®û
|
||||||
|
3 0 obj
|
||||||
|
<< /Length 4 0 R
|
||||||
|
/Filter /FlateDecode
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
xœ}RËN1¼û+ü˜Ø›‡ýHH
|
||||||
|
GÄ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çG‚E¨U9ÆsbÀŽy,2œ©•Sýβø ŠpF‚€}]$ŽÕ¹¥ÑÿE;£ýÃóùzC3šÌ›„’Ncô);¯ UÒ2á'
|
||||||
|
3•ZQ<EFBFBD>ÌŸWÀ¦Ô&:`cj¶7è÷\ÝJÍ^À§ªÕ_¦,°yXØ-Ó䶛ÏFÔOŸÖä;ìva +>ï:³Ã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
|
|
@ -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>
|
|
@ -33,7 +33,7 @@
|
||||||
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
|
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<connections>
|
<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>
|
</connections>
|
||||||
</refreshControl>
|
</refreshControl>
|
||||||
</tableViewController>
|
</tableViewController>
|
||||||
|
|
|
@ -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]
|
let status = statuses[indexPath.row]
|
||||||
guard status != mainStatus else { return }
|
return status == mainStatus ? nil : indexPath
|
||||||
guard let cell = tableView.cellForRow(at: indexPath) as? StatusTableViewCell else { fatalError() }
|
|
||||||
cell.didSelect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ class MainTabBarViewController: UITabBarController {
|
||||||
TimelineTableViewController.create(for: .home),
|
TimelineTableViewController.create(for: .home),
|
||||||
TimelineTableViewController.create(for: .federated),
|
TimelineTableViewController.create(for: .federated),
|
||||||
TimelineTableViewController.create(for: .local),
|
TimelineTableViewController.create(for: .local),
|
||||||
|
NotificationsTableViewController.create(),
|
||||||
PreferencesTableViewController.create()
|
PreferencesTableViewController.create()
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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) {
|
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||||
if indexPath.section == 1 && indexPath.row == statuses.count - 1 {
|
if indexPath.section == 1 && indexPath.row == statuses.count - 1 {
|
||||||
guard let older = older else { return }
|
guard let older = older else { return }
|
||||||
|
|
|
@ -103,11 +103,6 @@ class TimelineTableViewController: UITableViewController {
|
||||||
return cell
|
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) {
|
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||||
if indexPath.row == statuses.count - 1 {
|
if indexPath.row == statuses.count - 1 {
|
||||||
guard let older = older else { return }
|
guard let older = older else { return }
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -66,9 +66,7 @@ class ProfileHeaderTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
}
|
}
|
||||||
if let url = URL(string: account.header) {
|
if let url = URL(string: account.header) {
|
||||||
headerImageDownloadTask = URLSession.shared.dataTask(with: url) { data, response, error in
|
headerImageDownloadTask = URLSession.shared.dataTask(with: url) { data, response, error in
|
||||||
guard error == nil,
|
guard error == nil, let data = data, let image = UIImage(data: data) else { return }
|
||||||
let data = data,
|
|
||||||
let image = UIImage(data: data) else { return }
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.headerImageView.image = image
|
self.headerImageView.image = image
|
||||||
self.headerImageDownloadTask = nil
|
self.headerImageDownloadTask = nil
|
||||||
|
|
|
@ -44,9 +44,7 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
var reblogger: Account?
|
var reblogger: Account?
|
||||||
|
|
||||||
var avatarURL: URL?
|
var avatarURL: URL?
|
||||||
|
|
||||||
var updateTimestampWorkItem: DispatchWorkItem?
|
var updateTimestampWorkItem: DispatchWorkItem?
|
||||||
|
|
||||||
var attachmentDataTasks: [URLSessionDataTask] = []
|
var attachmentDataTasks: [URLSessionDataTask] = []
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
|
@ -61,6 +59,7 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
avatarImageView.layer.masksToBounds = true
|
avatarImageView.layer.masksToBounds = true
|
||||||
attachmentsView.layer.cornerRadius = 5
|
attachmentsView.layer.cornerRadius = 5
|
||||||
attachmentsView.layer.masksToBounds = true
|
attachmentsView.layer.masksToBounds = true
|
||||||
|
contentLabel.delegate = self
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIForPreferences() {
|
func updateUIForPreferences() {
|
||||||
|
@ -102,7 +101,6 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
}
|
}
|
||||||
updateTimestamp()
|
updateTimestamp()
|
||||||
|
|
||||||
attachmentsView.subviews.forEach { $0.removeFromSuperview() }
|
|
||||||
let attachments = status.mediaAttachments.filter({ $0.type == .image })
|
let attachments = status.mediaAttachments.filter({ $0.type == .image })
|
||||||
if attachments.count > 0 {
|
if attachments.count > 0 {
|
||||||
attachmentsView.isHidden = false
|
attachmentsView.isHidden = false
|
||||||
|
@ -132,7 +130,6 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
}
|
}
|
||||||
|
|
||||||
contentLabel.status = status
|
contentLabel.status = status
|
||||||
contentLabel.delegate = self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTimestamp() {
|
func updateTimestamp() {
|
||||||
|
@ -170,6 +167,15 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
updateTimestampWorkItem = nil
|
updateTimestampWorkItem = nil
|
||||||
attachmentsView.subviews.forEach { view in
|
attachmentsView.subviews.forEach { view in
|
||||||
(view as? AttachmentView)?.task?.cancel()
|
(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)
|
delegate?.selected(account: reblogger)
|
||||||
}
|
}
|
||||||
|
|
||||||
func didSelect() {
|
|
||||||
delegate?.selected(status: status)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StatusTableViewCell: HTMLContentLabelDelegate {
|
extension StatusTableViewCell: HTMLContentLabelDelegate {
|
||||||
|
|
Loading…
Reference in New Issue