Conversation view

This commit is contained in:
Shadowfacts 2018-08-28 14:29:06 -04:00
parent 05c895db88
commit 0f1a13d2a7
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
10 changed files with 390 additions and 12 deletions

View File

@ -14,6 +14,8 @@
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; }; D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; };
D64D0AAF2128D954005A6F37 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */; }; D64D0AAF2128D954005A6F37 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */; };
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; }; D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D663625C2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib */; };
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */; };
D667E5E12134937B0057A976 /* StatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D667E5E02134937B0057A976 /* StatusTableViewCell.xib */; }; D667E5E12134937B0057A976 /* StatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D667E5E02134937B0057A976 /* StatusTableViewCell.xib */; };
D667E5E3213499F70057A976 /* Profile.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D667E5E2213499F70057A976 /* Profile.storyboard */; }; D667E5E3213499F70057A976 /* Profile.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D667E5E2213499F70057A976 /* Profile.storyboard */; };
D667E5E721349D4C0057A976 /* ProfileTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5E621349D4C0057A976 /* ProfileTableViewController.swift */; }; D667E5E721349D4C0057A976 /* ProfileTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5E621349D4C0057A976 /* ProfileTableViewController.swift */; };
@ -21,6 +23,9 @@
D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5EA21349EF80057A976 /* ProfileHeaderTableViewCell.swift */; }; D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5EA21349EF80057A976 /* ProfileHeaderTableViewCell.swift */; };
D667E5EF2134C39F0057A976 /* StatusContentLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5EE2134C39F0057A976 /* StatusContentLabel.swift */; }; D667E5EF2134C39F0057A976 /* StatusContentLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5EE2134C39F0057A976 /* StatusContentLabel.swift */; };
D667E5F12134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F02134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift */; }; D667E5F12134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F02134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift */; };
D667E5F32135BC260057A976 /* Conversation.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D667E5F22135BC260057A976 /* Conversation.storyboard */; };
D667E5F52135BCD50057A976 /* ConversationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F42135BCD50057A976 /* ConversationViewController.swift */; };
D667E5F82135C3040057A976 /* Status+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F72135C3040057A976 /* Status+Equatable.swift */; };
D6BED16F212663DA00F02DA0 /* SwiftSoup.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; }; D6BED16F212663DA00F02DA0 /* SwiftSoup.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; };
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */; }; D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */; };
@ -77,6 +82,8 @@
D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; }; D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Onboarding.storyboard; sourceTree = "<group>"; }; D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Onboarding.storyboard; sourceTree = "<group>"; };
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; }; D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
D663625C2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConversationMainStatusTableViewCell.xib; sourceTree = "<group>"; };
D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMainStatusTableViewCell.swift; sourceTree = "<group>"; };
D667E5E02134937B0057A976 /* StatusTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StatusTableViewCell.xib; sourceTree = "<group>"; }; D667E5E02134937B0057A976 /* StatusTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StatusTableViewCell.xib; sourceTree = "<group>"; };
D667E5E2213499F70057A976 /* Profile.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Profile.storyboard; sourceTree = "<group>"; }; D667E5E2213499F70057A976 /* Profile.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Profile.storyboard; sourceTree = "<group>"; };
D667E5E621349D4C0057A976 /* ProfileTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTableViewController.swift; sourceTree = "<group>"; }; D667E5E621349D4C0057A976 /* ProfileTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTableViewController.swift; sourceTree = "<group>"; };
@ -84,6 +91,9 @@
D667E5EA21349EF80057A976 /* ProfileHeaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderTableViewCell.swift; sourceTree = "<group>"; }; D667E5EA21349EF80057A976 /* ProfileHeaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderTableViewCell.swift; sourceTree = "<group>"; };
D667E5EE2134C39F0057A976 /* StatusContentLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentLabel.swift; sourceTree = "<group>"; }; D667E5EE2134C39F0057A976 /* StatusContentLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentLabel.swift; sourceTree = "<group>"; };
D667E5F02134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+StatusTableViewCellDelegate.swift"; sourceTree = "<group>"; }; D667E5F02134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+StatusTableViewCellDelegate.swift"; sourceTree = "<group>"; };
D667E5F22135BC260057A976 /* Conversation.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Conversation.storyboard; sourceTree = "<group>"; };
D667E5F42135BCD50057A976 /* ConversationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationViewController.swift; sourceTree = "<group>"; };
D667E5F72135C3040057A976 /* Status+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Status+Equatable.swift"; sourceTree = "<group>"; };
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSoup.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSoup.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; }; D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; };
D6D4DDCC212518A000E1C4BB /* Tusker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Tusker.app; sourceTree = BUILT_PRODUCTS_DIR; }; D6D4DDCC212518A000E1C4BB /* Tusker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Tusker.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -131,6 +141,15 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
D667E5F62135C2ED0057A976 /* Extensions */ = {
isa = PBXGroup;
children = (
D667E5F02134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift */,
D667E5F72135C3040057A976 /* Status+Equatable.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
D6BED1722126661300F02DA0 /* Views */ = { D6BED1722126661300F02DA0 /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -138,9 +157,10 @@
D667E5EE2134C39F0057A976 /* StatusContentLabel.swift */, D667E5EE2134C39F0057A976 /* StatusContentLabel.swift */,
D667E5E02134937B0057A976 /* StatusTableViewCell.xib */, D667E5E02134937B0057A976 /* StatusTableViewCell.xib */,
D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */, D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */,
D667E5F02134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift */,
D667E5E821349EE50057A976 /* ProfileHeaderTableViewCell.xib */, D667E5E821349EE50057A976 /* ProfileHeaderTableViewCell.xib */,
D667E5EA21349EF80057A976 /* ProfileHeaderTableViewCell.swift */, D667E5EA21349EF80057A976 /* ProfileHeaderTableViewCell.swift */,
D663625C2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib */,
D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */,
); );
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
@ -174,6 +194,7 @@
04DACE89212CA6B7009840C4 /* Timeline.swift */, 04DACE89212CA6B7009840C4 /* Timeline.swift */,
D64D0AAC2128D88B005A6F37 /* LocalData.swift */, D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
04DACE8D212CC7CC009840C4 /* AvatarCache.swift */, 04DACE8D212CC7CC009840C4 /* AvatarCache.swift */,
D667E5F62135C2ED0057A976 /* Extensions */,
D6F953F121251A2F00CF0F2B /* Controllers */, D6F953F121251A2F00CF0F2B /* Controllers */,
D6F953E9212519B800CF0F2B /* View Controllers */, D6F953E9212519B800CF0F2B /* View Controllers */,
D6BED1722126661300F02DA0 /* Views */, D6BED1722126661300F02DA0 /* Views */,
@ -210,6 +231,7 @@
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */, D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */,
04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */, 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */,
D667E5E621349D4C0057A976 /* ProfileTableViewController.swift */, D667E5E621349D4C0057A976 /* ProfileTableViewController.swift */,
D667E5F42135BCD50057A976 /* ConversationViewController.swift */,
); );
path = "View Controllers"; path = "View Controllers";
sourceTree = "<group>"; sourceTree = "<group>";
@ -221,6 +243,7 @@
D6F953ED21251A0700CF0F2B /* Timeline.storyboard */, D6F953ED21251A0700CF0F2B /* Timeline.storyboard */,
D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */, D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */,
D667E5E2213499F70057A976 /* Profile.storyboard */, D667E5E2213499F70057A976 /* Profile.storyboard */,
D667E5F22135BC260057A976 /* Conversation.storyboard */,
); );
path = Storyboards; path = Storyboards;
sourceTree = "<group>"; sourceTree = "<group>";
@ -338,10 +361,12 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
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 */,
D64D0AAF2128D954005A6F37 /* Onboarding.storyboard in Resources */, D64D0AAF2128D954005A6F37 /* Onboarding.storyboard in Resources */,
D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */, D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */,
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */,
D6F953EE21251A0700CF0F2B /* Timeline.storyboard in Resources */, D6F953EE21251A0700CF0F2B /* Timeline.storyboard in Resources */,
D6D4DDD5212518A000E1C4BB /* Main.storyboard in Resources */, D6D4DDD5212518A000E1C4BB /* Main.storyboard in Resources */,
D667E5E3213499F70057A976 /* Profile.storyboard in Resources */, D667E5E3213499F70057A976 /* Profile.storyboard in Resources */,
@ -372,7 +397,9 @@
files = ( files = (
04DACE8A212CA6B7009840C4 /* Timeline.swift in Sources */, 04DACE8A212CA6B7009840C4 /* Timeline.swift in Sources */,
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */, 04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
D667E5F52135BCD50057A976 /* ConversationViewController.swift in Sources */,
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */, D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
D667E5F82135C3040057A976 /* Status+Equatable.swift in Sources */,
04DACE8E212CC7CC009840C4 /* AvatarCache.swift in Sources */, 04DACE8E212CC7CC009840C4 /* AvatarCache.swift in Sources */,
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */, D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */,
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */, D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
@ -382,6 +409,7 @@
D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */, D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */,
D64A0CD32132153900640E3B /* HTMLContentLabel.swift in Sources */, D64A0CD32132153900640E3B /* HTMLContentLabel.swift in Sources */,
D667E5F12134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift in Sources */, D667E5F12134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift in Sources */,
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */,
D667E5E721349D4C0057A976 /* ProfileTableViewController.swift in Sources */, D667E5E721349D4C0057A976 /* ProfileTableViewController.swift in Sources */,
D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */, D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */,
); );

View File

@ -0,0 +1,15 @@
//
// Status+Equatable.swift
// Tusker
//
// Created by Shadowfacts on 8/28/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import MastodonKit
extension Status: Equatable {
public static func ==(lhs: Status, rhs: Status) -> Bool {
return lhs.id == rhs.id
}
}

View File

@ -33,4 +33,12 @@ extension UIViewController: StatusTableViewCellDelegate {
present(vc, animated: true) present(vc, animated: true)
} }
func selected(status: Status) {
guard let navigationController = navigationController else {
fatalError("Can't show conversation VC when not in navigation controller")
}
let vc = ConversationViewController.create(for: status)
navigationController.pushViewController(vc, animated: true)
}
} }

View File

@ -0,0 +1,43 @@
<?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="3Yl-E3-qsN">
<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>
<scenes>
<!--Conversation View Controller-->
<scene sceneID="rkM-By-3Qj">
<objects>
<viewController id="3Yl-E3-qsN" customClass="ConversationViewController" customModule="Tusker" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="yCk-Ig-NdG">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="k5U-Kb-0kH">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="k5U-Kb-0kH" firstAttribute="leading" secondItem="mo7-1M-ylA" secondAttribute="leading" id="3Xe-vE-WWU"/>
<constraint firstItem="k5U-Kb-0kH" firstAttribute="top" secondItem="mo7-1M-ylA" secondAttribute="top" id="X2A-0c-iZD"/>
<constraint firstItem="mo7-1M-ylA" firstAttribute="bottom" secondItem="k5U-Kb-0kH" secondAttribute="bottom" id="bXb-L7-DfD"/>
<constraint firstItem="k5U-Kb-0kH" firstAttribute="trailing" secondItem="mo7-1M-ylA" secondAttribute="trailing" id="c6s-ef-Dxs"/>
</constraints>
<viewLayoutGuide key="safeArea" id="mo7-1M-ylA"/>
</view>
<connections>
<outlet property="tableView" destination="k5U-Kb-0kH" id="vAY-xd-xkg"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="BtE-Vw-c42" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-358" y="16"/>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,114 @@
//
// ConversationTableViewController.swift
// Tusker
//
// Created by Shadowfacts on 8/28/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import UIKit
import MastodonKit
class ConversationViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
static func create(for mainStatus: Status) -> ConversationViewController {
guard let conversationController = UIStoryboard(name: "Conversation", bundle: nil).instantiateInitialViewController() as? ConversationViewController else { fatalError() }
conversationController.mainStatus = mainStatus
return conversationController
}
@IBOutlet weak var tableView: UITableView!
var mainStatus: Status!
var statuses: [Status] = [] {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.register(UINib(nibName: "StatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell")
tableView.register(UINib(nibName: "ConversationMainStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "mainStatusCell")
statuses = [mainStatus]
let req = Statuses.context(id: mainStatus.id)
MastodonController.shared.client.run(req) { result in
guard case let .success(context, _) = result else { fatalError() }
var statuses = self.getDirectParents(of: self.mainStatus, from: context.ancestors)
statuses.append(self.mainStatus)
statuses.append(contentsOf: context.descendants)
self.statuses = statuses
let indexPath = IndexPath(row: statuses.firstIndex(of: self.mainStatus)!, section: 0)
DispatchQueue.main.async {
self.tableView.scrollToRow(at: indexPath, at: .middle, animated: false)
}
}
}
func getDirectParents(of status: Status, from statuses: [Status]) -> [Status] {
var statuses = statuses
var parents: [Status] = []
var currentStatus: Status? = status
while currentStatus != nil {
guard let index = statuses.firstIndex(where: { $0.id == currentStatus!.inReplyToID }) else { break }
let parent = statuses.remove(at: index)
parents.insert(parent, at: 0)
currentStatus = parent
}
return parents
}
/*
// 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
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return statuses.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let status = statuses[indexPath.row]
if status == mainStatus {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "mainStatusCell", for: indexPath) as? ConversationMainStatusTableViewCell else { fatalError() }
cell.selectionStyle = .none
cell.updateUI(for: status)
cell.delegate = self
return cell
} else {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() }
cell.updateUI(for: status)
cell.delegate = self
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt 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()
}
}

View File

@ -85,13 +85,13 @@ class ProfileTableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.section { switch indexPath.section {
case 0: case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: "headerCell", for: indexPath) as! ProfileHeaderTableViewCell guard let cell = tableView.dequeueReusableCell(withIdentifier: "headerCell", for: indexPath) as? ProfileHeaderTableViewCell else { fatalError() }
cell.selectionStyle = .none cell.selectionStyle = .none
cell.updateUI(for: account) cell.updateUI(for: account)
cell.delegate = self cell.delegate = self
return cell return cell
case 1: case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as! StatusTableViewCell guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() }
let status = statuses[indexPath.row] let status = statuses[indexPath.row]
cell.updateUI(for: status) cell.updateUI(for: status)
cell.delegate = self cell.delegate = self
@ -101,6 +101,12 @@ class ProfileTableViewController: UITableViewController {
} }
} }
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 }

View File

@ -8,7 +8,6 @@
import UIKit import UIKit
import MastodonKit import MastodonKit
import SafariServices
class TimelineTableViewController: UITableViewController { class TimelineTableViewController: UITableViewController {
@ -83,7 +82,7 @@ class TimelineTableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as! StatusTableViewCell guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() }
let status = statuses[indexPath.row] let status = statuses[indexPath.row]
@ -94,6 +93,11 @@ 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 }

View File

@ -0,0 +1,91 @@
//
// ConversationMainStatusTableViewCell.swift
// Tusker
//
// Created by Shadowfacts on 8/28/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import UIKit
import MastodonKit
class ConversationMainStatusTableViewCell: UITableViewCell {
var delegate: StatusTableViewCellDelegate?
@IBOutlet weak var displayNameLabel: UILabel!
@IBOutlet weak var usernameLabel: UILabel!
@IBOutlet weak var contentLabel: StatusContentLabel!
@IBOutlet weak var avatarImageView: UIImageView!
var status: Status!
var account: Account!
var avatarURL: URL?
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
avatarImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed)))
avatarImageView.isUserInteractionEnabled = true
avatarImageView.layer.cornerRadius = 5
avatarImageView.layer.masksToBounds = true
}
func updateUI(for status: Status) {
self.status = status
let account: Account
if let reblog = status.reblog {
account = reblog.account
} else {
account = status.account
}
self.account = account
displayNameLabel.text = account.displayName
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
}
}
}
contentLabel.status = status
contentLabel.delegate = self
}
override func prepareForReuse() {
if let url = avatarURL {
AvatarCache.shared.cancel(url)
}
}
@objc func accountPressed() {
delegate?.selected(account: account)
}
}
extension ConversationMainStatusTableViewCell: HTMLContentLabelDelegate {
func selected(mention: Mention) {
delegate?.selected(mention: mention)
}
func selected(tag: MastodonKit.Tag) {
delegate?.selected(tag: tag)
}
func selected(url: URL) {
delegate?.selected(url: url)
}
}

View File

@ -0,0 +1,70 @@
<?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="ConversationMainStatusTableViewCell" customModule="Tusker" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="200"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="mB9-HO-1vf">
<rect key="frame" x="16" y="8" width="50" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="XPF-UL-68q"/>
<constraint firstAttribute="width" constant="50" id="Yxp-Vr-dfl"/>
</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="lZY-2e-17d">
<rect key="frame" x="74" y="8" width="285" height="29"/>
<fontDescription key="fontDescription" type="system" pointSize="24"/>
<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="SWg-Ka-QyP">
<rect key="frame" x="74" y="37.5" width="285" 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>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TgY-hs-Klo" customClass="StatusContentLabel" customModule="Tusker" customModuleProvider="target">
<rect key="frame" x="16" y="66" width="343" height="126"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="TgY-hs-Klo" secondAttribute="trailing" constant="16" id="E3q-sb-FJE"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="lZY-2e-17d" secondAttribute="trailing" constant="16" id="ESw-bn-lfh"/>
<constraint firstItem="lZY-2e-17d" firstAttribute="leading" secondItem="mB9-HO-1vf" secondAttribute="trailing" constant="8" id="Egk-Kr-F8T"/>
<constraint firstItem="TgY-hs-Klo" firstAttribute="top" secondItem="mB9-HO-1vf" secondAttribute="bottom" constant="8" id="U7U-WR-0eG"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="SWg-Ka-QyP" secondAttribute="trailing" constant="16" id="YaX-Rf-6px"/>
<constraint firstItem="TgY-hs-Klo" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="Zn0-1F-UT0"/>
<constraint firstItem="mB9-HO-1vf" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" id="avn-ir-MvB"/>
<constraint firstItem="SWg-Ka-QyP" firstAttribute="leading" secondItem="mB9-HO-1vf" secondAttribute="trailing" constant="8" id="bAC-TW-LbN"/>
<constraint firstItem="mB9-HO-1vf" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="bqf-Es-GfP"/>
<constraint firstItem="SWg-Ka-QyP" firstAttribute="bottom" secondItem="mB9-HO-1vf" secondAttribute="bottom" id="c2R-gl-ybl"/>
<constraint firstItem="lZY-2e-17d" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" id="uWl-Wj-Zvr"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="TgY-hs-Klo" secondAttribute="bottom" constant="8" id="zlq-nS-Uff"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<connections>
<outlet property="avatarImageView" destination="mB9-HO-1vf" id="0R0-rt-Osh"/>
<outlet property="contentLabel" destination="TgY-hs-Klo" id="SEi-B2-VQf"/>
<outlet property="displayNameLabel" destination="lZY-2e-17d" id="7og-23-eHy"/>
<outlet property="usernameLabel" destination="SWg-Ka-QyP" id="h2I-g4-AD9"/>
</connections>
<point key="canvasLocation" x="40.799999999999997" y="-163.71814092953525"/>
</view>
</objects>
</document>

View File

@ -8,7 +8,6 @@
import UIKit import UIKit
import MastodonKit import MastodonKit
import SwiftSoup
protocol StatusTableViewCellDelegate { protocol StatusTableViewCellDelegate {
@ -20,6 +19,8 @@ protocol StatusTableViewCellDelegate {
func selected(url: URL) func selected(url: URL)
func selected(status: Status)
} }
class StatusTableViewCell: UITableViewCell { class StatusTableViewCell: UITableViewCell {
@ -36,12 +37,6 @@ class StatusTableViewCell: UITableViewCell {
var avatarURL: URL? var avatarURL: URL?
var layoutManager: NSLayoutManager!
var textContainer: NSTextContainer!
var textStorage: NSTextStorage!
var links: [NSRange: URL] = [:]
override func awakeFromNib() { override func awakeFromNib() {
displayNameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed))) displayNameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed)))
displayNameLabel.isUserInteractionEnabled = true displayNameLabel.isUserInteractionEnabled = true
@ -91,6 +86,10 @@ class StatusTableViewCell: UITableViewCell {
delegate?.selected(account: account) delegate?.selected(account: account)
} }
func didSelect() {
delegate?.selected(status: status)
}
} }
extension StatusTableViewCell: HTMLContentLabelDelegate { extension StatusTableViewCell: HTMLContentLabelDelegate {