Initial implementation Compose UI

This commit is contained in:
Shadowfacts 2018-08-30 22:30:19 -04:00
parent 481f493a9c
commit fc69b9684b
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
21 changed files with 866 additions and 50 deletions

View File

@ -10,6 +10,8 @@
04DACE8A212CA6B7009840C4 /* Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE89212CA6B7009840C4 /* Timeline.swift */; };
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; };
04DACE8E212CC7CC009840C4 /* AvatarCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* AvatarCache.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 */; };
D64A0CD32132153900640E3B /* HTMLContentLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64A0CD22132153900640E3B /* HTMLContentLabel.swift */; };
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; };
D64D0AAF2128D954005A6F37 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */; };
@ -22,6 +24,10 @@
D663626821360E2C00C9CBA2 /* PreferencesTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626721360E2C00C9CBA2 /* PreferencesTableViewController.swift */; };
D663626A2136163000C9CBA2 /* PreferencesAdaptive.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66362692136163000C9CBA2 /* PreferencesAdaptive.swift */; };
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626B21361C6700C9CBA2 /* Account+Preferences.swift */; };
D663626F213632A000C9CBA2 /* Compose.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D663626E213632A000C9CBA2 /* Compose.storyboard */; };
D66362712136338600C9CBA2 /* ComposeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66362702136338600C9CBA2 /* ComposeViewController.swift */; };
D66362732136FFC600C9CBA2 /* UITextView+Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66362722136FFC600C9CBA2 /* UITextView+Placeholder.swift */; };
D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */; };
D667E5E12134937B0057A976 /* StatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D667E5E02134937B0057A976 /* StatusTableViewCell.xib */; };
D667E5E3213499F70057A976 /* Profile.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D667E5E2213499F70057A976 /* Profile.storyboard */; };
D667E5E721349D4C0057A976 /* ProfileTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5E621349D4C0057A976 /* ProfileTableViewController.swift */; };
@ -84,6 +90,8 @@
04DACE89212CA6B7009840C4 /* Timeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timeline.swift; sourceTree = "<group>"; };
04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarViewController.swift; sourceTree = "<group>"; };
04DACE8D212CC7CC009840C4 /* AvatarCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarCache.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>"; };
D64A0CD22132153900640E3B /* HTMLContentLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLContentLabel.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>"; };
@ -97,6 +105,10 @@
D66362692136163000C9CBA2 /* PreferencesAdaptive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesAdaptive.swift; sourceTree = "<group>"; };
D663626B21361C6700C9CBA2 /* Account+Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account+Preferences.swift"; sourceTree = "<group>"; };
D663626D213629B300C9CBA2 /* MyPlayground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = MyPlayground.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
D663626E213632A000C9CBA2 /* Compose.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Compose.storyboard; sourceTree = "<group>"; };
D66362702136338600C9CBA2 /* ComposeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewController.swift; sourceTree = "<group>"; };
D66362722136FFC600C9CBA2 /* UITextView+Placeholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+Placeholder.swift"; sourceTree = "<group>"; };
D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Visibility+Helpers.swift"; 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>"; };
D667E5E621349D4C0057A976 /* ProfileTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTableViewController.swift; sourceTree = "<group>"; };
@ -170,6 +182,9 @@
D667E5F02134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift */,
D667E5F72135C3040057A976 /* Mastodon+Equatable.swift */,
D663626B21361C6700C9CBA2 /* Account+Preferences.swift */,
D66362722136FFC600C9CBA2 /* UITextView+Placeholder.swift */,
D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */,
D6333B362137838300CE884A /* AttributedString+Trim.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -185,6 +200,7 @@
D667E5EA21349EF80057A976 /* ProfileHeaderTableViewCell.swift */,
D663625C2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib */,
D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */,
D6333B762138D94E00CE884A /* ComposeMediaView.swift */,
);
path = Views;
sourceTree = "<group>";
@ -259,6 +275,7 @@
D667E5E621349D4C0057A976 /* ProfileTableViewController.swift */,
D667E5F42135BCD50057A976 /* ConversationViewController.swift */,
D663626721360E2C00C9CBA2 /* PreferencesTableViewController.swift */,
D66362702136338600C9CBA2 /* ComposeViewController.swift */,
);
path = "View Controllers";
sourceTree = "<group>";
@ -272,6 +289,7 @@
D667E5E2213499F70057A976 /* Profile.storyboard */,
D667E5F22135BC260057A976 /* Conversation.storyboard */,
D663626521360DD700C9CBA2 /* Preferences.storyboard */,
D663626E213632A000C9CBA2 /* Compose.storyboard */,
);
path = Storyboards;
sourceTree = "<group>";
@ -396,6 +414,7 @@
D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */,
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */,
D6F953EE21251A0700CF0F2B /* Timeline.storyboard in Resources */,
D663626F213632A000C9CBA2 /* Compose.storyboard in Resources */,
D6D4DDD5212518A000E1C4BB /* Main.storyboard in Resources */,
D667E5E3213499F70057A976 /* Profile.storyboard in Resources */,
D663626621360DD700C9CBA2 /* Preferences.storyboard in Resources */,
@ -428,16 +447,21 @@
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
D667E5F52135BCD50057A976 /* ConversationViewController.swift in Sources */,
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
D66362712136338600C9CBA2 /* ComposeViewController.swift in Sources */,
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
04DACE8E212CC7CC009840C4 /* AvatarCache.swift in Sources */,
D6333B772138D94E00CE884A /* ComposeMediaView.swift in Sources */,
D663626421360D2300C9CBA2 /* AvatarStyle.swift in Sources */,
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */,
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */,
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */,
D6333B372137838300CE884A /* AttributedString+Trim.swift in Sources */,
D667E5EF2134C39F0057A976 /* StatusContentLabel.swift in Sources */,
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */,
D663626821360E2C00C9CBA2 /* PreferencesTableViewController.swift in Sources */,
D66362732136FFC600C9CBA2 /* UITextView+Placeholder.swift in Sources */,
D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */,
D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */,
D663626A2136163000C9CBA2 /* PreferencesAdaptive.swift in Sources */,
D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */,

View File

@ -18,6 +18,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
if LocalData.shared.onboardingComplete {
MastodonController.shared.createClient()
MastodonController.shared.getOwnAccount()
} else {
showOnboarding()
}

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "Remove.pdf",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -0,0 +1,68 @@
%PDF-1.5
%µí®û
3 0 obj
<< /Length 4 0 R
/Filter /FlateDecode
>>
stream
xœePËN1 ¼û+æ0Žóò^{©„Ä¡\´¨­=ý}œlV° +rÆž;¹’ Åç ÷¯‚Ó´xÚ£rÆÍÛ~ÞéùÂ7JxÄÕMu×J1̪…Í"°—pÁÀE8 &D®yUôûàÏ ì‘~ú}^¥Ôá²ñcxþ·÷‡ñþ3Á¼ªÈ•UÔ¹”rŠ”ÈqòU”«NŽ³±XA¨œ:>Ó_‡c«$K]cæ×h¶îYÂâ™Ë¢èØgVº§ï9<7G:Ð7~s[O
endstream
endobj
4 0 obj
226
endobj
2 0 obj
<<
/ExtGState <<
/a0 << /CA 1 /ca 1 >>
>>
>>
endobj
5 0 obj
<< /Type /Page
/Parent 1 0 R
/MediaBox [ 0 0 80.631668 80.631668 ]
/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
0000000638 00000 n
0000000340 00000 n
0000000015 00000 n
0000000318 00000 n
0000000412 00000 n
0000000703 00000 n
0000000830 00000 n
trailer
<< /Size 8
/Root 7 0 R
/Info 6 0 R
>>
startxref
882
%%EOF

View File

@ -15,6 +15,8 @@ class MastodonController {
var client: Client!
var account: Account!
private init() {
}
@ -55,4 +57,12 @@ class MastodonController {
}
}
func getOwnAccount() {
let req = Accounts.currentUser()
client.run(req) { result in
guard case let .success(account, _) = result else { fatalError() }
self.account = account
}
}
}

View File

@ -0,0 +1,66 @@
//
// PlaceholderTextView.swift
// Tusker
//
// Created by Shadowfacts on 8/29/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import UIKit
// Source: https://finnwea.com/blog/adding-placeholders-to-uitextviews-in-swift/
extension UITextView: UITextViewDelegate {
override open var bounds: CGRect {
didSet {
resizePlaceholder()
}
}
var placeholder: String? {
get {
return (viewWithTag(100) as? UILabel)?.text
}
set {
if let placeholderLabel = viewWithTag(100) as? UILabel {
placeholderLabel.text = newValue
placeholderLabel.sizeToFit()
} else {
guard let newValue = newValue else { return }
addPlaceholder(newValue)
}
}
}
public func textViewDidChange(_ textView: UITextView) {
if let placeholderLabel = viewWithTag(100) as? UILabel {
placeholderLabel.isHidden = !text.isEmpty
}
}
private func resizePlaceholder() {
guard let placeholderLabel = viewWithTag(100) as? UILabel else { fatalError() }
let labelX = textContainer.lineFragmentPadding
let labelY = textContainerInset.top
let labelWidth = frame.width - (labelX * 2)
let labelHeight = placeholderLabel.frame.height
placeholderLabel.frame = CGRect(x: labelX, y: labelY, width: labelWidth, height: labelHeight)
}
private func addPlaceholder(_ placeholderText: String) {
let placeholderLabel = UILabel()
placeholderLabel.text = placeholderText
placeholderLabel.sizeToFit()
placeholderLabel.font = font
placeholderLabel.textColor = .lightGray
placeholderLabel.tag = 100
placeholderLabel.isHighlighted = !text.isEmpty
addSubview(placeholderLabel)
resizePlaceholder()
delegate = self
}
}

View File

@ -53,4 +53,9 @@ extension UIViewController: StatusTableViewCellDelegate {
navigationController.pushViewController(vc, animated: true)
}
func reply(to status: Status) {
let vc = ComposeViewController.create(inReplyTo: status)
present(vc, animated: true)
}
}

View File

@ -0,0 +1,30 @@
//
// Visibility+String.swift
// Tusker
//
// Created by Shadowfacts on 8/29/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import MastodonKit
extension Visibility {
static var allCases: [Visibility] {
return [.public, .unlisted, .private, .direct]
}
var displayName: String {
switch self {
case .public:
return "Public"
case .unlisted:
return "Unlisted"
case .private:
return "Private"
case .direct:
return "Direct"
}
}
}

View File

@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>To post photos from the camera.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>To post photos from the photo library.</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>

View File

@ -7,6 +7,7 @@
//
import Foundation
import MastodonKit
class Preferences: Codable {
@ -36,4 +37,6 @@ class Preferences: Codable {
var hideCustomEmojiInUsernames = false
var defaultPostVisibility = Visibility.public
}

View File

@ -0,0 +1,208 @@
<?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="8eV-YC-Spl">
<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>
<!--Compose-->
<scene sceneID="Dve-lH-JJK">
<objects>
<viewController id="svD-Ql-HGm" customClass="ComposeViewController" customModule="Tusker" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="qto-r6-ocp">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" keyboardDismissMode="onDrag" translatesAutoresizingMaskIntoConstraints="NO" id="L3H-aB-fGr">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="WID-nQ-ZzS">
<rect key="frame" x="16" y="16" width="343" height="735"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="l0G-YS-00D">
<rect key="frame" x="0.0" y="0.0" width="343" height="128"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="2fn-pv-lwV">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="L4b-xJ-V5E"/>
<constraint firstAttribute="width" constant="50" id="V0U-ps-ejP"/>
</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="kBr-of-4cF">
<rect key="frame" x="58" y="0.0" width="105" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="@username" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="neM-1F-PcJ">
<rect key="frame" x="171" y="0.0" width="172" 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" text="Content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WcY-cJ-FqF" customClass="StatusContentLabel" customModule="Tusker" customModuleProvider="target">
<rect key="frame" x="58" y="28.5" width="285" height="99.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="2fn-pv-lwV" firstAttribute="top" secondItem="l0G-YS-00D" secondAttribute="top" id="17U-sX-oMw"/>
<constraint firstAttribute="bottom" secondItem="WcY-cJ-FqF" secondAttribute="bottom" id="EVN-o1-tfI"/>
<constraint firstItem="neM-1F-PcJ" firstAttribute="leading" secondItem="kBr-of-4cF" secondAttribute="trailing" constant="8" id="Iy1-we-N0r"/>
<constraint firstAttribute="trailing" secondItem="neM-1F-PcJ" secondAttribute="trailing" id="XK2-sV-Cgu"/>
<constraint firstItem="WcY-cJ-FqF" firstAttribute="leading" secondItem="2fn-pv-lwV" secondAttribute="trailing" constant="8" id="ZuW-k7-y14"/>
<constraint firstItem="WcY-cJ-FqF" firstAttribute="top" secondItem="kBr-of-4cF" secondAttribute="bottom" constant="8" id="bwQ-WL-uN8"/>
<constraint firstAttribute="trailing" secondItem="WcY-cJ-FqF" secondAttribute="trailing" id="jUB-n1-YWr"/>
<constraint firstItem="neM-1F-PcJ" firstAttribute="top" secondItem="l0G-YS-00D" secondAttribute="top" id="mam-bN-10G"/>
<constraint firstItem="kBr-of-4cF" firstAttribute="top" secondItem="l0G-YS-00D" secondAttribute="top" id="vgj-G0-dv2"/>
<constraint firstItem="kBr-of-4cF" firstAttribute="leading" secondItem="2fn-pv-lwV" secondAttribute="trailing" constant="8" id="yZV-4x-7s6"/>
<constraint firstItem="2fn-pv-lwV" firstAttribute="leading" secondItem="l0G-YS-00D" secondAttribute="leading" id="zdv-5N-Xyl"/>
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="bGo-tx-r56">
<rect key="frame" x="0.0" y="136" width="343" height="599"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="In reply to Display Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="NGI-fk-d7F">
<rect key="frame" x="0.0" y="0.0" width="343" 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>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="gQU-rc-mM7">
<rect key="frame" x="0.0" y="26.5" width="343" height="200"/>
<color key="backgroundColor" red="0.8980392157" green="0.8980392157" blue="0.91764705879999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="200" id="ugS-rN-LrW"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
<stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="cfA-uo-q9g">
<rect key="frame" x="0.0" y="232.5" width="343" height="30"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wW4-Zd-GH4">
<rect key="frame" x="0.0" y="0.0" width="42" height="30"/>
<state key="normal" title="Media"/>
<connections>
<action selector="mediaPressed:" destination="svD-Ql-HGm" eventType="touchUpInside" id="0pB-L2-VZa"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="EOO-If-BeL">
<rect key="frame" x="50" y="0.0" width="30" height="30"/>
<state key="normal" title="CW"/>
<connections>
<action selector="contentWarningPressed:" destination="svD-Ql-HGm" eventType="touchUpInside" id="O1a-3w-Ofr"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="caP-6L-HWj">
<rect key="frame" x="88" y="0.0" width="42" height="30"/>
<state key="normal" title="Public"/>
<connections>
<action selector="visibilityPressed:" destination="svD-Ql-HGm" eventType="touchUpInside" id="aeI-Wi-7Mz"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wg4-nL-Q7B">
<rect key="frame" x="138" y="0.0" width="205" height="30"/>
<state key="normal" title="Post"/>
<connections>
<action selector="postPressed:" destination="svD-Ql-HGm" eventType="touchUpInside" id="HqO-kb-qcz"/>
</connections>
</button>
</subviews>
</stackView>
<textField hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Content Warning" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="Rbm-SO-Cpp">
<rect key="frame" x="0.0" y="265.5" width="343" height="0.0"/>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" returnKeyType="done"/>
</textField>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="gcH-tf-prn">
<rect key="frame" x="0.0" y="268.5" width="343" height="100"/>
<constraints>
<constraint firstAttribute="height" constant="100" id="0BL-1Y-g90"/>
</constraints>
</stackView>
<view contentMode="scaleToFill" verticalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="VuB-eX-IiL">
<rect key="frame" x="0.0" y="374.5" width="343" height="224.5"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
</stackView>
</subviews>
</stackView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="WID-nQ-ZzS" secondAttribute="trailing" constant="16" id="0Sy-jt-Ddv"/>
<constraint firstItem="bGo-tx-r56" firstAttribute="height" secondItem="L3H-aB-fGr" secondAttribute="height" constant="-68" id="5Xh-ao-jAN"/>
<constraint firstItem="WID-nQ-ZzS" firstAttribute="leading" secondItem="L3H-aB-fGr" secondAttribute="leading" constant="16" id="8Aw-l2-092"/>
<constraint firstItem="WID-nQ-ZzS" firstAttribute="top" secondItem="L3H-aB-fGr" secondAttribute="top" constant="16" id="CYg-ft-CAr"/>
<constraint firstAttribute="bottom" secondItem="WID-nQ-ZzS" secondAttribute="bottom" id="yTD-d9-Cr8"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="L3H-aB-fGr" firstAttribute="leading" secondItem="qto-r6-ocp" secondAttribute="leading" id="01l-qs-Ff4"/>
<constraint firstItem="L3H-aB-fGr" firstAttribute="top" secondItem="qto-r6-ocp" secondAttribute="topMargin" constant="-64" id="7BB-7k-gaN"/>
<constraint firstItem="WID-nQ-ZzS" firstAttribute="width" secondItem="qto-r6-ocp" secondAttribute="width" constant="-32" id="Fer-8I-o1H"/>
<constraint firstAttribute="trailing" secondItem="L3H-aB-fGr" secondAttribute="trailing" id="NWJ-dh-7ED"/>
<constraint firstItem="7ws-AM-scc" firstAttribute="bottom" secondItem="L3H-aB-fGr" secondAttribute="bottom" id="uZs-Y8-hGM"/>
</constraints>
<viewLayoutGuide key="safeArea" id="7ws-AM-scc"/>
</view>
<navigationItem key="navigationItem" title="Compose" id="WVz-mo-nCC">
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="94u-El-jk4">
<connections>
<segue destination="XCi-Fs-398" kind="unwind" identifier="cancel" unwindAction="unwindToTabBarControllerWithSegue:" id="R9N-eg-Lp6"/>
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="contentWarningTextField" destination="Rbm-SO-Cpp" id="d1F-yi-CnV"/>
<outlet property="inReplyToAvatarImageView" destination="2fn-pv-lwV" id="SMy-L1-bI1"/>
<outlet property="inReplyToContainerView" destination="l0G-YS-00D" id="XO0-HW-WVf"/>
<outlet property="inReplyToContentLabel" destination="WcY-cJ-FqF" id="JNr-hO-QPO"/>
<outlet property="inReplyToDisplayNameLabel" destination="kBr-of-4cF" id="bWk-5w-vJb"/>
<outlet property="inReplyToLabel" destination="NGI-fk-d7F" id="S59-D5-9bF"/>
<outlet property="inReplyToUsernameLabel" destination="neM-1F-PcJ" id="Lej-ut-yp9"/>
<outlet property="mediaStackView" destination="gcH-tf-prn" id="oM2-hc-rXY"/>
<outlet property="paddingView" destination="VuB-eX-IiL" id="wDo-4O-g30"/>
<outlet property="scrollView" destination="L3H-aB-fGr" id="kbS-H5-K2I"/>
<outlet property="statusTextView" destination="gQU-rc-mM7" id="FVL-nk-159"/>
<outlet property="visibilityButton" destination="caP-6L-HWj" id="89p-Bl-cRH"/>
<segue destination="XCi-Fs-398" kind="unwind" identifier="postComplete" unwindAction="unwindToTabBarControllerWithSegue:" id="00m-ok-xPA"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="jeI-ty-LsU" userLabel="First Responder" sceneMemberID="firstResponder"/>
<exit id="XCi-Fs-398" userLabel="Exit" sceneMemberID="exit"/>
</objects>
<point key="canvasLocation" x="1048.8" y="-157.87106446776613"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="RHQ-fP-dlc">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="8eV-YC-Spl" sceneMemberID="viewController">
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="PYB-mm-ur8">
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<nil name="viewControllers"/>
<connections>
<segue destination="svD-Ql-HGm" kind="relationship" relationship="rootViewController" id="WUM-0u-sCZ"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="RLe-aq-Yb1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="109.59999999999999" y="-157.87106446776613"/>
</scene>
</scenes>
</document>

View File

@ -21,8 +21,15 @@
<outlet property="delegate" destination="HMn-Wn-5Ab" id="PbX-SY-1Qw"/>
</connections>
</tableView>
<navigationItem key="navigationItem" id="bvr-ex-deZ"/>
<navigationItem key="navigationItem" id="bvr-ex-deZ">
<barButtonItem key="rightBarButtonItem" systemItem="compose" id="4lI-Md-Vmw">
<connections>
<action selector="composePressed:" destination="HMn-Wn-5Ab" id="TRT-Dp-Rwx"/>
</connections>
</barButtonItem>
</navigationItem>
<refreshControl key="refreshControl" opaque="NO" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" id="aAC-id-7YO">
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
<autoresizingMask key="autoresizingMask"/>
<connections>
<action selector="refreshStatuses:" destination="HMn-Wn-5Ab" eventType="valueChanged" id="uPt-pK-1iL"/>

View File

@ -21,7 +21,13 @@
<outlet property="delegate" destination="6nb-nb-cMm" id="Dio-U3-a5Z"/>
</connections>
</tableView>
<navigationItem key="navigationItem" id="SQS-FM-ReS"/>
<navigationItem key="navigationItem" id="SQS-FM-ReS">
<barButtonItem key="rightBarButtonItem" systemItem="compose" id="nVG-dM-Pvp">
<connections>
<segue destination="Xq5-zT-4uE" kind="presentation" id="2rg-sr-zDQ"/>
</connections>
</barButtonItem>
</navigationItem>
<simulatedTabBarMetrics key="simulatedBottomBarMetrics"/>
<refreshControl key="refreshControl" opaque="NO" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" id="K3b-fx-67z">
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
@ -35,6 +41,14 @@
</objects>
<point key="canvasLocation" x="800.79999999999995" y="-90.404797601199405"/>
</scene>
<!--Compose-->
<scene sceneID="bcB-Kb-Axl">
<objects>
<viewControllerPlaceholder storyboardName="Compose" id="Xq5-zT-4uE" sceneMemberID="viewController"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="T2a-NG-JV4" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1410" y="-359"/>
</scene>
<!--Item-->
<scene sceneID="yNQ-ej-ymp">
<objects>

View File

@ -0,0 +1,245 @@
//
// ComposeViewController.swift
// Tusker
//
// Created by Shadowfacts on 8/28/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import UIKit
import MastodonKit
class ComposeViewController: UIViewController {
static func create(inReplyTo: Status? = nil, mentioning: Account? = nil) -> UIViewController {
guard let navigationController = UIStoryboard(name: "Compose", bundle: nil).instantiateInitialViewController() as? UINavigationController,
let composeVC = navigationController.topViewController as? ComposeViewController else { fatalError() }
composeVC.inReplyTo = inReplyTo
composeVC.mentioning = mentioning
return navigationController
}
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var inReplyToContainerView: UIView!
@IBOutlet weak var inReplyToAvatarImageView: UIImageView!
@IBOutlet weak var inReplyToDisplayNameLabel: UILabel!
@IBOutlet weak var inReplyToUsernameLabel: UILabel!
@IBOutlet weak var inReplyToContentLabel: StatusContentLabel!
@IBOutlet weak var inReplyToLabel: UILabel!
@IBOutlet weak var statusTextView: UITextView!
@IBOutlet weak var visibilityButton: UIButton!
@IBOutlet weak var contentWarningTextField: UITextField!
@IBOutlet weak var mediaStackView: UIStackView!
@IBOutlet weak var paddingView: UIView!
var scrolled = false
var inReplyTo: Status?
var mentioning: Account?
var contentWarning = false
var visibility = Preferences.shared.defaultPostVisibility
var status: Status?
override func viewDidLoad() {
super.viewDidLoad()
statusTextView.placeholder = "What is on your mind?"
statusTextView.layer.cornerRadius = 5
statusTextView.layer.masksToBounds = true
visibilityButton.setTitle(visibility.displayName, for: .normal)
contentWarningTextField.delegate = self
let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 30))
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let done = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(keyboardDoneButtonPressed))
toolbar.setItems([flexSpace, done], animated: false)
toolbar.sizeToFit()
statusTextView.inputAccessoryView = toolbar
if let inReplyTo = inReplyTo {
inReplyToDisplayNameLabel.text = inReplyTo.account.realDisplayName
inReplyToUsernameLabel.text = "@\(inReplyTo.account.username)"
inReplyToContentLabel.status = inReplyTo
inReplyToAvatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: inReplyToAvatarImageView)
inReplyToAvatarImageView.layer.masksToBounds = true
inReplyToAvatarImageView.image = nil
if let url = URL(string: inReplyTo.account.avatar) {
AvatarCache.shared.get(url) { image in
DispatchQueue.main.async {
self.inReplyToAvatarImageView.image = image
}
}
}
inReplyToLabel.text = "In reply to \(inReplyTo.account.realDisplayName)"
if inReplyTo.account != MastodonController.shared.account {
statusTextView.text = "@\(inReplyTo.account.acct) "
}
statusTextView.text += inReplyTo.mentions.filter({ $0.id != MastodonController.shared.account.id }).map({ "@\($0.acct) " }).joined()
statusTextView.textViewDidChange(statusTextView)
} else {
inReplyToLabel.isHidden = true
inReplyToContainerView.isHidden = true
}
if let mentioning = mentioning {
statusTextView.text += "@\(mentioning.acct) "
statusTextView.textViewDidChange(statusTextView)
}
}
override func viewDidLayoutSubviews() {
if inReplyTo != nil && !scrolled {
scrollView.contentOffset = CGPoint(x: 0, y: inReplyToContainerView.bounds.height - 44)
scrolled = true
}
}
func addMedia(for image: UIImage) {
let mediaView = ComposeMediaView(image: image)
mediaStackView.addArrangedSubview(mediaView)
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
statusTextView.resignFirstResponder()
contentWarningTextField.resignFirstResponder()
if segue.identifier == "postComplete" {
guard let status = status else { fatalError("postComplete segue can't occur without Status") }
guard let dest = segue.destination as? MainTabBarViewController,
let navController = dest.selectedViewController as? UINavigationController,
let topVC = navController.topViewController else { return }
topVC.selected(status: status)
}
}
// MARK: - Interaction
@IBAction func visibilityPressed(_ sender: Any) {
let alertController = UIAlertController(title: "Post Visibility", message: nil, preferredStyle: .actionSheet)
for visibility in Visibility.allCases {
let action = UIAlertAction(title: visibility.displayName, style: .default, handler: { _ in
self.visibility = visibility
UIView.performWithoutAnimation {
self.visibilityButton.setTitle(visibility.displayName, for: .normal)
self.visibilityButton.layoutIfNeeded()
}
})
if visibility == self.visibility {
action.setValue(true, forKey: "checked")
}
alertController.addAction(action)
}
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alertController, animated: true)
}
@IBAction func contentWarningPressed(_ sender: Any) {
contentWarning = !contentWarning
contentWarningTextField.isHidden = !contentWarning
}
@IBAction func mediaPressed(_ sender: Any) {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
let alertController = UIAlertController(title: "Choose Image Source", message: nil, preferredStyle: .actionSheet)
if UIImagePickerController.isSourceTypeAvailable(.camera) {
alertController.addAction(UIAlertAction(title: "Camera", style: .default, handler: { _ in
imagePicker.sourceType = .camera
self.present(imagePicker, animated: true)
}))
}
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
alertController.addAction(UIAlertAction(title: "Photo Library", style: .default, handler: { __ in
imagePicker.sourceType = .photoLibrary
self.present(imagePicker, animated: true)
}))
}
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alertController, animated: true)
}
@IBAction func postPressed(_ sender: Any) {
guard let text = statusTextView.text,
!text.isEmpty else { return }
let inReplyToID = inReplyTo?.id
let contentWarning: String?
if self.contentWarning,
let text = contentWarningTextField.text,
!text.isEmpty {
contentWarning = text
} else {
contentWarning = nil
}
let sensitive = contentWarning != nil
let visibility = self.visibility
var attachments: [Attachment?] = []
let group = DispatchGroup()
for view in mediaStackView.arrangedSubviews {
guard let imageView = view as? UIImageView,
let image = imageView.image,
let data = image.pngData() else { continue }
let index = attachments.count
attachments.append(nil)
group.enter()
let req = Media.upload(media: .png(data))
MastodonController.shared.client.run(req) { result in
guard case let .success(attachment, _) = result else { fatalError() }
attachments[index] = attachment
group.leave()
}
}
group.notify(queue: .main) {
let mediaIDs = attachments.map { $0!.id }
let req = Statuses.create(status: text,
replyToID: inReplyToID,
mediaIDs: mediaIDs,
sensitive: sensitive,
spoilerText: contentWarning,
visibility: visibility)
MastodonController.shared.client.run(req) { result in
guard case let .success(status, _) = result else { fatalError() }
self.status = status
DispatchQueue.main.async {
self.performSegue(withIdentifier: "postComplete", sender: self)
}
}
}
}
@objc func imagePressed(_ gesture: UITapGestureRecognizer) {
gesture.view!.superview!.removeFromSuperview()
}
@objc func keyboardDoneButtonPressed() {
statusTextView.endEditing(false)
}
}
extension ComposeViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.endEditing(false)
return true
}
}
extension ComposeViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let selectedImage = info[.originalImage] as? UIImage {
addMedia(for: selectedImage)
dismiss(animated: true)
}
}
}

View File

@ -32,4 +32,7 @@ class MainTabBarViewController: UITabBarController {
}
*/
@IBAction func unwindToTabBarController(segue: UIStoryboardSegue) {
}
}

View File

@ -47,6 +47,8 @@ class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
updateUIForPreferences()
MastodonController.shared.client.run(request()) { result in
guard case let .success(statuses, pagination) = result else { fatalError() }
self.statuses = statuses
@ -70,6 +72,11 @@ class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
navigationItem.title = account.realDisplayName
}
func sendMessageMentioning() {
let vc = ComposeViewController.create(mentioning: account)
present(vc, animated: true)
}
/*
// MARK: - Navigation
@ -146,6 +153,11 @@ class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
}
}
}
@IBAction func composePressed(_ sender: Any) {
sendMessageMentioning()
}
}
extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate {
@ -161,7 +173,7 @@ extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate {
self.present(vc, animated: true)
}))
alert.addAction(UIAlertAction(title: "Send Message...", style: .default, handler: { _ in
print("send message to @\(self.account.acct)")
self.sendMessageMentioning()
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true)

View File

@ -0,0 +1,44 @@
//
// ComposeAttachmentView.swift
// Tusker
//
// Created by Shadowfacts on 8/30/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import UIKit
class ComposeMediaView: UIImageView {
var remove: UIImageView
required init?(coder aDecoder: NSCoder) {
return nil
}
init(image: UIImage) {
remove = UIImageView(image: UIImage(named: "Remove"))
super.init(image: image)
contentMode = .scaleAspectFill
layer.cornerRadius = 5
layer.masksToBounds = true
isUserInteractionEnabled = true
remove.isUserInteractionEnabled = true
remove.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(removePressed)))
remove.translatesAutoresizingMaskIntoConstraints = false
addSubview(remove)
remove.widthAnchor.constraint(equalToConstant: 20).isActive = true
remove.heightAnchor.constraint(equalToConstant: 20).isActive = true
remove.topAnchor.constraint(equalTo: topAnchor, constant: 5).isActive = true
remove.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -5).isActive = true
}
@objc func removePressed() {
removeFromSuperview()
}
}

View File

@ -77,6 +77,10 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive
delegate?.selected(account: account)
}
@IBAction func replyPressed(_ sender: Any) {
delegate?.reply(to: status)
}
}
extension ConversationMainStatusTableViewCell: HTMLContentLabelDelegate {

View File

@ -15,46 +15,75 @@
<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>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="GuG-Qd-B8I">
<rect key="frame" x="16" y="8" width="343" height="184"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Cnd-Fj-B7l">
<rect key="frame" x="0.0" y="0.0" width="343" height="146"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="mB9-HO-1vf">
<rect key="frame" x="0.0" y="0.0" 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="58" y="0.0" 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="58" y="29" 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="0.0" y="58" width="343" height="88"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="TgY-hs-Klo" firstAttribute="leading" secondItem="Cnd-Fj-B7l" secondAttribute="leading" id="2zu-G9-fMv"/>
<constraint firstAttribute="trailing" secondItem="SWg-Ka-QyP" secondAttribute="trailing" id="4g6-BT-eW4"/>
<constraint firstAttribute="bottom" secondItem="TgY-hs-Klo" secondAttribute="bottom" id="5Og-Pd-Vck"/>
<constraint firstItem="lZY-2e-17d" firstAttribute="top" secondItem="Cnd-Fj-B7l" secondAttribute="top" id="8fU-y9-K5Z"/>
<constraint firstItem="lZY-2e-17d" firstAttribute="leading" secondItem="mB9-HO-1vf" secondAttribute="trailing" constant="8" id="Aqj-co-Szp"/>
<constraint firstItem="mB9-HO-1vf" firstAttribute="top" secondItem="Cnd-Fj-B7l" secondAttribute="top" id="R7P-rD-Gbm"/>
<constraint firstAttribute="trailing" secondItem="TgY-hs-Klo" secondAttribute="trailing" id="SOE-Q5-IWd"/>
<constraint firstItem="mB9-HO-1vf" firstAttribute="leading" secondItem="Cnd-Fj-B7l" secondAttribute="leading" id="bxq-Fs-1aH"/>
<constraint firstItem="SWg-Ka-QyP" firstAttribute="leading" secondItem="mB9-HO-1vf" secondAttribute="trailing" constant="8" id="e45-gE-myI"/>
<constraint firstItem="TgY-hs-Klo" firstAttribute="top" secondItem="mB9-HO-1vf" secondAttribute="bottom" constant="8" id="l6y-Rr-Nmc"/>
<constraint firstItem="SWg-Ka-QyP" firstAttribute="top" secondItem="lZY-2e-17d" secondAttribute="bottom" id="lvX-1b-8cN"/>
<constraint firstAttribute="trailing" secondItem="lZY-2e-17d" secondAttribute="trailing" id="qcG-N7-OIv"/>
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="3Bg-XP-d13">
<rect key="frame" x="0.0" y="154" width="343" height="30"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="leading" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2cc-lE-AdG">
<rect key="frame" x="0.0" y="0.0" width="343" height="30"/>
<state key="normal" title="Reply"/>
<connections>
<action selector="replyPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="hsh-gx-Swo"/>
</connections>
</button>
</subviews>
</stackView>
</subviews>
</stackView>
</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"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="GuG-Qd-B8I" secondAttribute="bottom" constant="8" id="2F3-0f-0tC"/>
<constraint firstItem="GuG-Qd-B8I" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="PFH-TI-ZQ9"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="GuG-Qd-B8I" secondAttribute="trailing" constant="16" id="WpU-W0-YkL"/>
<constraint firstItem="GuG-Qd-B8I" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" id="oMG-LP-HUD"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>

View File

@ -21,6 +21,8 @@ protocol StatusTableViewCellDelegate {
func selected(status: Status)
func reply(to status: Status)
}
class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
@ -99,6 +101,10 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
}
}
@IBAction func replyPressed(_ sender: Any) {
delegate?.reply(to: status)
}
@objc func accountPressed() {
delegate?.selected(account: account)
}

View File

@ -12,20 +12,20 @@
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="StatusTableViewCell" customModule="Tusker" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="101"/>
<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="yNh-ac-v6c">
<rect key="frame" x="16" y="8" width="343" height="85"/>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="yNh-ac-v6c">
<rect key="frame" x="16" y="8" width="343" height="142"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Reblogged by Person" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lDH-50-AJZ">
<rect key="frame" x="0.0" y="0.0" width="163.5" height="20.5"/>
<rect key="frame" x="0.0" y="0.0" width="343" 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>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ve3-Y1-NQH">
<rect key="frame" x="0.0" y="28.5" width="343" height="56.5"/>
<view contentMode="scaleToFill" verticalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="ve3-Y1-NQH">
<rect key="frame" x="0.0" y="28.5" width="343" height="75.5"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="QMP-j2-HLn">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
@ -43,7 +43,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HrJ-t9-KcD" customClass="StatusContentLabel" customModule="Tusker" customModuleProvider="target">
<rect key="frame" x="58" y="28.5" width="285" height="28"/>
<rect key="frame" x="58" y="28.5" width="285" height="47"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@ -62,7 +62,7 @@
<constraint firstAttribute="trailing" secondItem="j89-zc-SFa" secondAttribute="trailing" id="D9z-3W-FHn"/>
<constraint firstItem="QMP-j2-HLn" firstAttribute="top" secondItem="ve3-Y1-NQH" secondAttribute="top" id="PC4-Bi-QXm"/>
<constraint firstItem="j89-zc-SFa" firstAttribute="leading" secondItem="gll-xe-FSr" secondAttribute="trailing" constant="8" id="XGL-3q-Urc"/>
<constraint firstAttribute="bottom" secondItem="HrJ-t9-KcD" secondAttribute="bottom" id="XUe-ft-BPV"/>
<constraint firstAttribute="bottom" secondItem="HrJ-t9-KcD" secondAttribute="bottom" id="YAm-mK-YXb"/>
<constraint firstItem="gll-xe-FSr" firstAttribute="leading" secondItem="QMP-j2-HLn" secondAttribute="trailing" constant="8" id="f00-ER-g7Q"/>
<constraint firstItem="gll-xe-FSr" firstAttribute="top" secondItem="ve3-Y1-NQH" secondAttribute="top" id="ikJ-qd-ziU"/>
<constraint firstAttribute="trailing" secondItem="HrJ-t9-KcD" secondAttribute="trailing" id="wCB-bW-AdR"/>
@ -70,13 +70,25 @@
<constraint firstItem="QMP-j2-HLn" firstAttribute="leading" secondItem="ve3-Y1-NQH" secondAttribute="leading" id="zeW-tQ-uJl"/>
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="Zlb-yt-NTw">
<rect key="frame" x="0.0" y="112" width="343" height="30"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" contentHorizontalAlignment="leading" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rKF-yF-KIa">
<rect key="frame" x="0.0" y="0.0" width="343" height="30"/>
<state key="normal" title="Reply"/>
<connections>
<action selector="replyPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="Ohg-uU-d3Z"/>
</connections>
</button>
</subviews>
</stackView>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="yNh-ac-v6c" secondAttribute="trailing" constant="16" id="2qQ-80-4Ui"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="yNh-ac-v6c" secondAttribute="bottom" constant="8" id="RKQ-BR-mlH"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="yNh-ac-v6c" secondAttribute="bottom" id="RKQ-BR-mlH"/>
<constraint firstItem="yNh-ac-v6c" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="YVV-xZ-N4f"/>
<constraint firstItem="yNh-ac-v6c" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" id="aAG-d7-dmV"/>
</constraints>
@ -89,7 +101,7 @@
<outlet property="reblogLabel" destination="lDH-50-AJZ" id="uJf-Pt-cEP"/>
<outlet property="usernameLabel" destination="j89-zc-SFa" id="see-Xd-3e9"/>
</connections>
<point key="canvasLocation" x="40.799999999999997" y="27.436281859070466"/>
<point key="canvasLocation" x="29.600000000000001" y="38.680659670164921"/>
</view>
</objects>
</document>