Browse Source

Add GIF support

master
Dwayne Harris 6 years ago
parent
commit
4c6bb677f1
  1. 8
      elpha-ios.xcodeproj/project.pbxproj
  2. 4
      elpha-ios/AbstractStatusTableViewController.swift
  3. 13
      elpha-ios/AccountTableViewController.swift
  4. 12
      elpha-ios/Assets.xcassets/Icons/Close White.imageset/Contents.json
  5. BIN
      elpha-ios/Assets.xcassets/Icons/Close White.imageset/x-square.pdf
  6. BIN
      elpha-ios/Assets.xcassets/Icons/Close.imageset/x-square.pdf
  7. BIN
      elpha-ios/Assets.xcassets/Icons/Help.imageset/help-circle.pdf
  8. 42
      elpha-ios/AttachmentManager.swift
  9. 5
      elpha-ios/AttachmentViewController.swift
  10. 319
      elpha-ios/Base.lproj/Main.storyboard
  11. 64
      elpha-ios/ComposeViewController.swift
  12. 78
      elpha-ios/FLAnimatedImageView+LoadImageURL.swift
  13. 3
      elpha-ios/MainStatusTableViewCell.swift
  14. 13
      elpha-ios/SettingsTableViewController.swift
  15. 13
      elpha-ios/StatusTableViewController.swift
  16. 33
      elpha-ios/StatusView.swift
  17. 15
      elpha-ios/StatusView.xib
  18. 22
      elpha-ios/TimelineTableViewController.swift

8
elpha-ios.xcodeproj/project.pbxproj

@ -24,6 +24,8 @@
152FBCEE219799FC0079B3E8 /* FLAnimatedImage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 152FBCEC219799E50079B3E8 /* FLAnimatedImage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
152FBCF2219818AD0079B3E8 /* FieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 152FBCF1219818AD0079B3E8 /* FieldTableViewCell.swift */; };
1539509121894A38009BA6E7 /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1539509021894A38009BA6E7 /* AlertManager.swift */; };
156902A0219A7D75002BF61F /* ComposeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1569029F219A7D75002BF61F /* ComposeViewController.swift */; };
156902B3219A8A1C002BF61F /* FLAnimatedImageView+LoadImageURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 156902B2219A8A1C002BF61F /* FLAnimatedImageView+LoadImageURL.swift */; };
156FF015217289380074D9CA /* AccountTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 156FF014217289380074D9CA /* AccountTableViewCell.swift */; };
156FF0312174797E0074D9CA /* StatusTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 156FF0302174797E0074D9CA /* StatusTableViewController.swift */; };
156FF04F2175CDBC0074D9CA /* MainStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 156FF04E2175CDBC0074D9CA /* MainStatusTableViewCell.swift */; };
@ -256,6 +258,8 @@
152FBCE7219799E50079B3E8 /* FLAnimatedImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = FLAnimatedImage.xcodeproj; path = Frameworks/FLAnimatedImage/FLAnimatedImage.xcodeproj; sourceTree = "<group>"; };
152FBCF1219818AD0079B3E8 /* FieldTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldTableViewCell.swift; sourceTree = "<group>"; };
1539509021894A38009BA6E7 /* AlertManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertManager.swift; sourceTree = "<group>"; };
1569029F219A7D75002BF61F /* ComposeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewController.swift; sourceTree = "<group>"; };
156902B2219A8A1C002BF61F /* FLAnimatedImageView+LoadImageURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FLAnimatedImageView+LoadImageURL.swift"; sourceTree = "<group>"; };
156FF014217289380074D9CA /* AccountTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTableViewCell.swift; sourceTree = "<group>"; };
156FF0302174797E0074D9CA /* StatusTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewController.swift; sourceTree = "<group>"; };
156FF04E2175CDBC0074D9CA /* MainStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainStatusTableViewCell.swift; sourceTree = "<group>"; };
@ -378,6 +382,7 @@
isa = PBXGroup;
children = (
159026CF2163069600D362DD /* Date+TimeAgo.swift */,
156902B2219A8A1C002BF61F /* FLAnimatedImageView+LoadImageURL.swift */,
15B127912192467F00F4EF1D /* String+HtmlAttributed.swift */,
15B127A22192486200F4EF1D /* UIColor+HexString.swift */,
);
@ -500,6 +505,7 @@
152FB0F7218ADC1A001D6574 /* AttachmentPageViewController.swift */,
152FB0F9218ADDD0001D6574 /* AttachmentViewController.swift */,
15960E7D21329FED00C38CE9 /* AuthenticateViewController.swift */,
1569029F219A7D75002BF61F /* ComposeViewController.swift */,
1506C5922199768A00EFB483 /* InstancesNavigationController.swift */,
15960E83213774FC00C38CE9 /* InstancesTableViewController.swift */,
157405A72150588A00EEAAEB /* InstanceViewController.swift */,
@ -751,6 +757,7 @@
156FF0312174797E0074D9CA /* StatusTableViewController.swift in Sources */,
152FB0F8218ADC1A001D6574 /* AttachmentPageViewController.swift in Sources */,
15131EF4216DB8B90092B252 /* AccountTableViewController.swift in Sources */,
156902A0219A7D75002BF61F /* ComposeViewController.swift in Sources */,
15F998352162C0E8009E58DA /* MastodonDataManager.swift in Sources */,
15960E7A2132387A00C38CE9 /* MainTabBarController.swift in Sources */,
15960E7C213272CD00C38CE9 /* AuthenticationManager.swift in Sources */,
@ -773,6 +780,7 @@
159026D02163069600D362DD /* Date+TimeAgo.swift in Sources */,
152FBCD2219682E80079B3E8 /* AbstractStatusTableViewController.swift in Sources */,
15A79B43215EB959007A326E /* CoreDataManager.swift in Sources */,
156902B3219A8A1C002BF61F /* FLAnimatedImageView+LoadImageURL.swift in Sources */,
15BB72AB2171A8D4002F1FA4 /* TimelinesTableViewCell.swift in Sources */,
1574148D2169AD0100C841BD /* AttachmentManager.swift in Sources */,
156FF015217289380074D9CA /* AccountTableViewCell.swift in Sources */,

4
elpha-ios/AbstractStatusTableViewController.swift

@ -48,6 +48,10 @@ class AbstractStatusTableViewController: UITableViewController, StatusViewDelega
}
func replyTapped(status: StatusMO) {
performSegue(withIdentifier: "ComposeSegue", sender: self)
}
func attachmentTapped(status: StatusMO, index: Int) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)

13
elpha-ios/AccountTableViewController.swift

@ -8,6 +8,7 @@
import AlamofireImage
import CoreData
import FLAnimatedImage
import UIKit
import SafariServices
@ -65,7 +66,7 @@ class FieldTableViewController: NSObject, UITableViewDelegate, UITableViewDataSo
class AccountTableViewController: AbstractStatusTableViewController {
@IBOutlet var headerView: UIView!
@IBOutlet var headerImageView: UIImageView!
@IBOutlet var avatarImageView: UIImageView!
@IBOutlet var avatarImageView: FLAnimatedImageView!
@IBOutlet var displayNameLabel: UILabel!
@IBOutlet var usernameLabel: UILabel!
@IBOutlet var statusesLabel: UILabel!
@ -101,18 +102,12 @@ class AccountTableViewController: AbstractStatusTableViewController {
private func updateHeader(withAccount account: AccountMO) {
navigationItem.title = account.displayName
let avatarFilter = AspectScaledToFillSizeWithRoundedCornersFilter(
size: CGSize(width: 70.0, height: 70.0),
radius: 20.0,
divideRadiusByImageScale: true
)
if let headerURL = account.headerURL {
headerImageView.af_setImage(withURL: headerURL)
}
if let avatarURL = account.avatarURL {
avatarImageView.af_setImage(withURL: avatarURL, filter: avatarFilter)
avatarImageView.loadImageURL(avatarURL)
}
displayNameLabel.text = account.displayName
@ -148,6 +143,8 @@ class AccountTableViewController: AbstractStatusTableViewController {
avatarImageView.layer.shadowOpacity = 0.8
avatarImageView.layer.shadowOffset = CGSize.zero
avatarImageView.layer.shadowRadius = 10
avatarImageView.layer.cornerRadius = 10
avatarImageView.layer.masksToBounds = true
if self.account == nil {
if let session = AuthenticationManager.session {

12
elpha-ios/Assets.xcassets/Icons/Close White.imageset/Contents.json

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "x-square.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

BIN
elpha-ios/Assets.xcassets/Icons/Close White.imageset/x-square.pdf

BIN
elpha-ios/Assets.xcassets/Icons/Close.imageset/x-square.pdf

BIN
elpha-ios/Assets.xcassets/Icons/Help.imageset/help-circle.pdf

42
elpha-ios/AttachmentManager.swift

@ -17,10 +17,6 @@ protocol AttachmentManagerDelegate {
class AttachmentManager: NSObject {
var delegate: AttachmentManagerDelegate? = nil
static private var downloader = ImageDownloader()
static private var imageCache = AutoPurgingImageCache()
static private let placeholderImage = UIImage(named: "Placeholder")
@objc func attachmentTapped(_ gestureRecognizer: UITapGestureRecognizer) {
if let delegate = delegate, let name = gestureRecognizer.name {
delegate.attachmentTapped(index: Int(name)!)
@ -42,8 +38,6 @@ class AttachmentManager: NSObject {
case 0:
return
case 1:
let filter = AspectScaledToFillSizeFilter(size: CGSize(width: view.frame.width, height: view.frame.width))
let attachment = attachments.firstObject as! AttachmentMO
let imageView = FLAnimatedImageView()
@ -55,20 +49,8 @@ class AttachmentManager: NSObject {
imageView.addGestureRecognizer(tapGestureRecognizer)
imageView.isUserInteractionEnabled = true
imageView.contentMode = UIImageView.ContentMode.center
imageView.image = AttachmentManager.placeholderImage
let request = URLRequest(url: attachment.url!)
AttachmentManager.downloader.download(request, filter: filter) { response in
imageView.contentMode = UIImageView.ContentMode.scaleAspectFill
switch attachment.type {
case "gif":
imageView.animatedImage = FLAnimatedImage(animatedGIFData: response.data)
default:
imageView.image = response.result.value
}
}
imageView.loadImageURL(attachment.url!)
view.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
@ -80,8 +62,6 @@ class AttachmentManager: NSObject {
imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
case 2:
let filter = AspectScaledToFillSizeFilter(size: CGSize(width: halfWidth, height: view.frame.width))
let imageViews = [
FLAnimatedImageView(),
FLAnimatedImageView(),
@ -99,10 +79,7 @@ class AttachmentManager: NSObject {
imageView.addGestureRecognizer(tapGestureRecognizer)
imageView.isUserInteractionEnabled = true
imageView.af_setImage(
withURL: (attachments[index] as! AttachmentMO).url!,
filter: filter
)
imageView.loadImageURL((attachments[index] as! AttachmentMO).url!)
view.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
@ -119,9 +96,6 @@ class AttachmentManager: NSObject {
imageViews[1].trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
case 3:
let primaryFilter = AspectScaledToFillSizeFilter(size: CGSize(width: halfWidth, height: view.frame.width))
let secondaryFilter = AspectScaledToFillSizeFilter(size: CGSize(width: halfWidth, height: halfWidth))
let imageViews = [
FLAnimatedImageView(),
FLAnimatedImageView(),
@ -140,10 +114,7 @@ class AttachmentManager: NSObject {
imageView.addGestureRecognizer(tapGestureRecognizer)
imageView.isUserInteractionEnabled = true
imageView.af_setImage(
withURL: (attachments[index] as! AttachmentMO).url!,
filter: index == 0 ? primaryFilter : secondaryFilter
)
imageView.loadImageURL((attachments[index] as! AttachmentMO).url!)
view.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
@ -169,8 +140,6 @@ class AttachmentManager: NSObject {
imageViews[2].bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
default:
let filter = AspectScaledToFillSizeFilter(size: CGSize(width: halfWidth, height: halfWidth))
let imageViews = [
FLAnimatedImageView(),
FLAnimatedImageView(),
@ -190,10 +159,7 @@ class AttachmentManager: NSObject {
imageView.addGestureRecognizer(tapGestureRecognizer)
imageView.isUserInteractionEnabled = true
imageView.af_setImage(
withURL: (attachments[index] as! AttachmentMO).url!,
filter: filter
)
imageView.loadImageURL((attachments[index] as! AttachmentMO).url!)
view.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false

5
elpha-ios/AttachmentViewController.swift

@ -6,11 +6,12 @@
// Copyright © 2018 Elpha. All rights reserved.
//
import FLAnimatedImage
import UIKit
class AttachmentViewController: UIViewController {
@IBOutlet var attachmentScrollView: UIScrollView!
@IBOutlet var attachmentImageView: UIImageView!
@IBOutlet var attachmentImageView: FLAnimatedImageView!
@IBOutlet var statusTextView: UITextView!
var attachment: AttachmentMO? = nil
@ -33,7 +34,7 @@ class AttachmentViewController: UIViewController {
if let attachment = attachment {
attachmentScrollView.delegate = self
attachmentImageView.af_setImage(withURL: attachment.url!)
attachmentImageView.loadImageURL(attachment.url!)
if let content = attachment.status?.content {
statusTextView.attributedText = content.htmlAttributed(size: 14, centered: true, color: UIColor.white)

319
elpha-ios/Base.lproj/Main.storyboard

@ -493,7 +493,7 @@
<constraint firstAttribute="height" constant="150" id="HWQ-9W-TLG"/>
</constraints>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="d7X-u9-Seq">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="d7X-u9-Seq" customClass="FLAnimatedImageView">
<rect key="frame" x="152.66666666666666" y="115" width="70" height="70"/>
<constraints>
<constraint firstAttribute="height" constant="70" id="6Ni-kg-YNd"/>
@ -807,7 +807,7 @@
<constraint firstAttribute="height" constant="120" id="ql5-nX-1Yo"/>
</constraints>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Aeb-Oz-GwY">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Aeb-Oz-GwY" customClass="FLAnimatedImageView">
<rect key="frame" x="152.66666666666666" y="85" width="70" height="70"/>
<constraints>
<constraint firstAttribute="width" constant="70" id="LTv-YQ-bzk"/>
@ -1059,99 +1059,251 @@
</objects>
<point key="canvasLocation" x="1632.8" y="-1094.3349753694581"/>
</scene>
<!--View Controller-->
<!--Compose View Controller-->
<scene sceneID="K94-uj-No4">
<objects>
<viewController id="MNp-Rq-Swn" sceneMemberID="viewController">
<viewController storyboardIdentifier="ComposeViewController" definesPresentationContext="YES" providesPresentationContextTransitionStyle="YES" modalPresentationStyle="overCurrentContext" id="MNp-Rq-Swn" customClass="ComposeViewController" customModule="elpha_ios" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="NDm-1a-rKP">
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="Type something..." borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="XJL-y7-EDA">
<rect key="frame" x="20" y="64" width="335" height="522"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="Content Warning" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="WUG-su-Fog">
<rect key="frame" x="20" y="691" width="335" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="Wsd-wk-qNZ"/>
</constraints>
<color key="textColor" cocoaTouchSystemColor="scrollViewTexturedBackgroundColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bRl-St-wBw">
<rect key="frame" x="15" y="623" width="345" height="60"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="p9x-bR-vKb">
<rect key="frame" x="8" y="15" width="80" height="30"/>
<state key="normal" title="Attachment">
<color key="titleColor" name="Primary"/>
</state>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="YtP-mc-9ah">
<rect key="frame" x="103" y="15" width="45" height="30"/>
<state key="normal" title="GIPHY">
<color key="titleColor" name="Primary"/>
</state>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3r6-Fu-wdF">
<rect key="frame" x="306" y="15" width="31" height="30"/>
<state key="normal" title="Toot">
<color key="titleColor" name="Primary"/>
</state>
</button>
</subviews>
<constraints>
<constraint firstItem="YtP-mc-9ah" firstAttribute="centerY" secondItem="bRl-St-wBw" secondAttribute="centerY" id="8Cj-1i-Jlh"/>
<constraint firstItem="YtP-mc-9ah" firstAttribute="leading" secondItem="p9x-bR-vKb" secondAttribute="trailing" constant="15" id="MWV-Jg-4rk"/>
<constraint firstAttribute="trailing" secondItem="3r6-Fu-wdF" secondAttribute="trailing" constant="8" id="Qq0-N5-ZPl"/>
<constraint firstAttribute="height" constant="60" id="gWv-dI-qJe"/>
<constraint firstItem="p9x-bR-vKb" firstAttribute="centerY" secondItem="bRl-St-wBw" secondAttribute="centerY" id="lXI-Fi-kX0"/>
<constraint firstItem="3r6-Fu-wdF" firstAttribute="centerY" secondItem="bRl-St-wBw" secondAttribute="centerY" id="mIt-5o-KTB"/>
<constraint firstItem="p9x-bR-vKb" firstAttribute="leading" secondItem="bRl-St-wBw" secondAttribute="leading" constant="8" id="u9D-K8-ghg"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="500" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="urp-v1-YyC">
<rect key="frame" x="20" y="594" width="335" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" name="Primary"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Description of content warnings." lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iMd-HH-Dfv">
<rect key="frame" x="20" y="749" width="335" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" cocoaTouchSystemColor="scrollViewTexturedBackgroundColor"/>
<nil key="highlightedColor"/>
</label>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bv7-e3-oWD">
<rect key="frame" x="0.0" y="0.0" width="375" height="729"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="s1W-PY-h1k">
<rect key="frame" x="0.0" y="0.0" width="375" height="729"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TaE-kM-jSH">
<rect key="frame" x="0.0" y="0.0" width="375" height="729"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="yeC-3y-hAG">
<rect key="frame" x="0.0" y="50" width="375" height="729"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="OUa-cb-0gQ">
<rect key="frame" x="0.0" y="0.0" width="375" height="30"/>
<subviews>
<imageView contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Close" translatesAutoresizingMaskIntoConstraints="NO" id="7bB-Lp-9yL">
<rect key="frame" x="20" y="6" width="18" height="18"/>
<gestureRecognizers/>
<constraints>
<constraint firstAttribute="height" constant="18" id="aZg-Zz-VMv"/>
<constraint firstAttribute="width" constant="18" id="bRZ-Sb-1Sk"/>
</constraints>
<connections>
<outletCollection property="gestureRecognizers" destination="0V9-Ll-no6" appends="YES" id="Ojt-oP-apL"/>
</connections>
</imageView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="2zB-SW-8LS"/>
<constraint firstItem="7bB-Lp-9yL" firstAttribute="centerY" secondItem="OUa-cb-0gQ" secondAttribute="centerY" id="i8p-kO-8p4"/>
<constraint firstItem="7bB-Lp-9yL" firstAttribute="leading" secondItem="OUa-cb-0gQ" secondAttribute="leading" constant="20" id="yNQ-qM-Bnd"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="w3S-pT-NYF">
<rect key="frame" x="0.0" y="30" width="375" height="250"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Replying To" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="co5-cA-nTI">
<rect key="frame" x="20" y="8" width="335" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="YlZ-hK-ikz"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" name="Primary"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="DBM-gJ-bmW" customClass="FLAnimatedImageView">
<rect key="frame" x="20" y="73" width="55" height="55"/>
<constraints>
<constraint firstAttribute="height" constant="55" id="Odb-PW-C0m"/>
<constraint firstAttribute="width" constant="55" id="eJR-OK-tSg"/>
</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="htY-mN-l6s">
<rect key="frame" x="83" y="73" width="272" height="23"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="19"/>
<color key="textColor" name="Text"/>
<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="P5v-hq-2Ee">
<rect key="frame" x="83" y="98" width="272" height="19.333333333333329"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" name="Primary"/>
<nil key="highlightedColor"/>
</label>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Content" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="wi5-07-ou8" customClass="UITextViewFixed" customModule="elpha_ios" customModuleProvider="target">
<rect key="frame" x="20" y="143" width="335" height="87"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="tintColor" name="Primary"/>
<color key="textColor" name="Text"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<dataDetectorType key="dataDetectorTypes" phoneNumber="YES" link="YES" address="YES" calendarEvent="YES" shipmentTrackingNumber="YES" flightNumber="YES" lookupSuggestion="YES"/>
</textView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="P5v-hq-2Ee" firstAttribute="top" secondItem="htY-mN-l6s" secondAttribute="bottom" constant="2" id="3cF-2A-z1a"/>
<constraint firstItem="htY-mN-l6s" firstAttribute="top" secondItem="co5-cA-nTI" secondAttribute="bottom" constant="15" id="5q8-gy-TNz"/>
<constraint firstItem="co5-cA-nTI" firstAttribute="leading" secondItem="w3S-pT-NYF" secondAttribute="leading" constant="20" id="AcU-cS-MkN"/>
<constraint firstItem="htY-mN-l6s" firstAttribute="leading" secondItem="DBM-gJ-bmW" secondAttribute="trailing" constant="8" id="BoK-Jc-mUZ"/>
<constraint firstAttribute="trailing" secondItem="wi5-07-ou8" secondAttribute="trailing" constant="20" id="LH6-kW-CnZ"/>
<constraint firstAttribute="bottom" secondItem="wi5-07-ou8" secondAttribute="bottom" constant="20" id="V6M-9G-xOh"/>
<constraint firstAttribute="trailing" secondItem="P5v-hq-2Ee" secondAttribute="trailing" constant="20" id="W4Q-IT-0Oe"/>
<constraint firstItem="wi5-07-ou8" firstAttribute="top" secondItem="DBM-gJ-bmW" secondAttribute="bottom" constant="15" id="Wad-bN-IVn"/>
<constraint firstItem="wi5-07-ou8" firstAttribute="leading" secondItem="w3S-pT-NYF" secondAttribute="leading" constant="20" id="YlR-MT-7L7"/>
<constraint firstItem="DBM-gJ-bmW" firstAttribute="top" secondItem="co5-cA-nTI" secondAttribute="bottom" constant="15" id="gJf-bQ-lJH"/>
<constraint firstItem="P5v-hq-2Ee" firstAttribute="leading" secondItem="DBM-gJ-bmW" secondAttribute="trailing" constant="8" id="gqO-Uc-pq3"/>
<constraint firstItem="co5-cA-nTI" firstAttribute="top" secondItem="w3S-pT-NYF" secondAttribute="top" constant="8" id="qIa-AE-nyn"/>
<constraint firstAttribute="trailing" secondItem="htY-mN-l6s" secondAttribute="trailing" constant="20" id="s3Z-Vx-dco"/>
<constraint firstAttribute="trailing" secondItem="co5-cA-nTI" secondAttribute="trailing" constant="20" id="sCf-x8-mNG"/>
<constraint firstItem="DBM-gJ-bmW" firstAttribute="leading" secondItem="w3S-pT-NYF" secondAttribute="leading" constant="20" id="tFt-Ki-sef"/>
<constraint firstAttribute="height" constant="250" id="zVv-ls-E8L"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LeJ-qN-f3j">
<rect key="frame" x="0.0" y="280" width="375" height="449"/>
<subviews>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" borderStyle="roundedRect" placeholder="Type something..." textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="FWE-IU-mUe">
<rect key="frame" x="20" y="8" width="335" height="253"/>
<color key="textColor" name="Text"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<textInputTraits key="textInputTraits"/>
<connections>
<action selector="statusTextFieldEdited:" destination="MNp-Rq-Swn" eventType="editingDidEnd" id="9L6-su-P47"/>
</connections>
</textField>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Content Warning" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="D8y-nm-aIL">
<rect key="frame" x="20" y="363" width="335" height="40"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="Pja-qP-5Hb"/>
</constraints>
<color key="textColor" name="Text"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<textInputTraits key="textInputTraits"/>
</textField>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kek-uj-Dhl">
<rect key="frame" x="20" y="295" width="335" height="60"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ClN-BL-Y5Y">
<rect key="frame" x="0.0" y="15" width="80" height="30"/>
<state key="normal" title="Attachment">
<color key="titleColor" name="Primary"/>
</state>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uTI-IW-IxV">
<rect key="frame" x="95" y="15" width="45" height="30"/>
<state key="normal" title="GIPHY">
<color key="titleColor" name="Primary"/>
</state>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="60a-gb-CaD">
<rect key="frame" x="289" y="12" width="46" height="36"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
<state key="normal" title=" Toot">
<color key="titleColor" name="Primary"/>
</state>
</button>
</subviews>
<constraints>
<constraint firstItem="ClN-BL-Y5Y" firstAttribute="leading" secondItem="kek-uj-Dhl" secondAttribute="leading" id="2qT-cZ-WPa"/>
<constraint firstAttribute="trailing" secondItem="60a-gb-CaD" secondAttribute="trailing" id="F21-ZR-wIR"/>
<constraint firstItem="uTI-IW-IxV" firstAttribute="centerY" secondItem="kek-uj-Dhl" secondAttribute="centerY" id="OI6-mv-RDN"/>
<constraint firstAttribute="height" constant="60" id="kO5-xQ-oya"/>
<constraint firstItem="60a-gb-CaD" firstAttribute="centerY" secondItem="kek-uj-Dhl" secondAttribute="centerY" id="l6J-J9-XEH"/>
<constraint firstItem="uTI-IW-IxV" firstAttribute="leading" secondItem="ClN-BL-Y5Y" secondAttribute="trailing" constant="15" id="mGi-mX-tDA"/>
<constraint firstItem="ClN-BL-Y5Y" firstAttribute="centerY" secondItem="kek-uj-Dhl" secondAttribute="centerY" id="wm3-uo-Dp9"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="500" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lj5-34-gkN">
<rect key="frame" x="20" y="269" width="335" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" name="Primary"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Description of content warnings." lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="L6c-Q0-xOK">
<rect key="frame" x="20" y="411" width="335" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" cocoaTouchSystemColor="scrollViewTexturedBackgroundColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="lj5-34-gkN" secondAttribute="trailing" constant="20" id="1Ww-Jb-slE"/>
<constraint firstItem="lj5-34-gkN" firstAttribute="top" secondItem="FWE-IU-mUe" secondAttribute="bottom" constant="8" id="4Vf-VW-dZw"/>
<constraint firstAttribute="trailing" secondItem="D8y-nm-aIL" secondAttribute="trailing" constant="20" id="Ghs-6F-hzK"/>
<constraint firstItem="D8y-nm-aIL" firstAttribute="top" secondItem="kek-uj-Dhl" secondAttribute="bottom" constant="8" id="Hg7-Bi-qm9"/>
<constraint firstItem="L6c-Q0-xOK" firstAttribute="top" secondItem="D8y-nm-aIL" secondAttribute="bottom" constant="8" id="IIM-Kg-fbe"/>
<constraint firstAttribute="trailing" secondItem="kek-uj-Dhl" secondAttribute="trailing" constant="20" id="MAi-pv-PLM"/>
<constraint firstItem="D8y-nm-aIL" firstAttribute="leading" secondItem="LeJ-qN-f3j" secondAttribute="leading" constant="20" id="Tdy-Mo-HPS"/>
<constraint firstAttribute="bottom" secondItem="L6c-Q0-xOK" secondAttribute="bottom" constant="20" id="XkQ-Ab-eUM"/>
<constraint firstItem="lj5-34-gkN" firstAttribute="leading" secondItem="LeJ-qN-f3j" secondAttribute="leading" constant="20" id="bAc-FO-r0d"/>
<constraint firstItem="kek-uj-Dhl" firstAttribute="top" secondItem="lj5-34-gkN" secondAttribute="bottom" constant="8" id="bgR-6t-h4g"/>
<constraint firstItem="FWE-IU-mUe" firstAttribute="leading" secondItem="LeJ-qN-f3j" secondAttribute="leading" constant="20" id="ha4-jU-BVV"/>
<constraint firstAttribute="trailing" secondItem="FWE-IU-mUe" secondAttribute="trailing" constant="20" id="of8-Dh-4P0"/>
<constraint firstItem="L6c-Q0-xOK" firstAttribute="leading" secondItem="LeJ-qN-f3j" secondAttribute="leading" constant="20" id="pwG-Io-phi"/>
<constraint firstItem="kek-uj-Dhl" firstAttribute="leading" secondItem="LeJ-qN-f3j" secondAttribute="leading" constant="20" id="qKf-dD-i3Z"/>
<constraint firstItem="FWE-IU-mUe" firstAttribute="top" secondItem="LeJ-qN-f3j" secondAttribute="top" constant="8" id="uDV-vG-DPW"/>
<constraint firstAttribute="trailing" secondItem="L6c-Q0-xOK" secondAttribute="trailing" constant="20" id="z6e-Op-san"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="yeC-3y-hAG" firstAttribute="top" secondItem="TaE-kM-jSH" secondAttribute="top" constant="50" id="0rg-i7-uGd"/>
<constraint firstItem="yeC-3y-hAG" firstAttribute="width" secondItem="TaE-kM-jSH" secondAttribute="width" id="HD2-Z9-XVR"/>
<constraint firstItem="yeC-3y-hAG" firstAttribute="leading" secondItem="TaE-kM-jSH" secondAttribute="leading" id="haA-5q-EG2"/>
<constraint firstAttribute="bottom" secondItem="yeC-3y-hAG" secondAttribute="bottom" id="iTf-Cg-o06"/>
<constraint firstItem="yeC-3y-hAG" firstAttribute="height" secondItem="TaE-kM-jSH" secondAttribute="height" id="imE-TK-nOE"/>
<constraint firstAttribute="trailing" secondItem="yeC-3y-hAG" secondAttribute="trailing" id="rh3-9A-KfJ"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="TaE-kM-jSH" firstAttribute="leading" secondItem="s1W-PY-h1k" secondAttribute="leading" id="E9Q-ZS-xWL"/>
<constraint firstItem="TaE-kM-jSH" firstAttribute="top" secondItem="s1W-PY-h1k" secondAttribute="top" id="Xyy-bK-L6U"/>
<constraint firstAttribute="trailing" secondItem="TaE-kM-jSH" secondAttribute="trailing" id="u7p-wI-aDf"/>
<constraint firstAttribute="bottom" secondItem="TaE-kM-jSH" secondAttribute="bottom" id="uV9-Tt-dTp"/>
</constraints>
</view>
<blurEffect style="light"/>
</visualEffectView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="iMd-HH-Dfv" firstAttribute="top" secondItem="WUG-su-Fog" secondAttribute="bottom" constant="8" id="3ET-fx-Eq6"/>
<constraint firstItem="17A-fV-wOt" firstAttribute="trailing" secondItem="XJL-y7-EDA" secondAttribute="trailing" constant="20" id="9cl-YR-22N"/>
<constraint firstItem="WUG-su-Fog" firstAttribute="leading" secondItem="17A-fV-wOt" secondAttribute="leading" constant="20" id="CtJ-sd-FcH"/>
<constraint firstItem="17A-fV-wOt" firstAttribute="trailing" secondItem="bRl-St-wBw" secondAttribute="trailing" constant="15" id="Cxd-vT-MqH"/>
<constraint firstItem="XJL-y7-EDA" firstAttribute="top" secondItem="17A-fV-wOt" secondAttribute="top" constant="20" id="G5U-KX-u09"/>
<constraint firstItem="17A-fV-wOt" firstAttribute="bottom" secondItem="iMd-HH-Dfv" secondAttribute="bottom" constant="8" id="GcA-JN-wkC"/>
<constraint firstItem="bRl-St-wBw" firstAttribute="leading" secondItem="17A-fV-wOt" secondAttribute="leading" constant="15" id="KAU-gs-6Uo"/>
<constraint firstItem="WUG-su-Fog" firstAttribute="top" secondItem="bRl-St-wBw" secondAttribute="bottom" constant="8" id="Liy-3h-Coq"/>
<constraint firstItem="17A-fV-wOt" firstAttribute="trailing" secondItem="iMd-HH-Dfv" secondAttribute="trailing" constant="20" id="O22-rm-0oM"/>
<constraint firstItem="urp-v1-YyC" firstAttribute="leading" secondItem="17A-fV-wOt" secondAttribute="leading" constant="20" id="QkN-Rp-orl"/>
<constraint firstItem="urp-v1-YyC" firstAttribute="top" secondItem="XJL-y7-EDA" secondAttribute="bottom" constant="8" id="VA7-Hl-Zov"/>
<constraint firstItem="17A-fV-wOt" firstAttribute="trailing" secondItem="WUG-su-Fog" secondAttribute="trailing" constant="20" id="aPC-Uo-01w"/>
<constraint firstItem="bRl-St-wBw" firstAttribute="top" secondItem="urp-v1-YyC" secondAttribute="bottom" constant="8" id="hrG-7p-JA7"/>
<constraint firstItem="iMd-HH-Dfv" firstAttribute="leading" secondItem="17A-fV-wOt" secondAttribute="leading" constant="20" id="meg-sw-z4I"/>
<constraint firstItem="XJL-y7-EDA" firstAttribute="leading" secondItem="17A-fV-wOt" secondAttribute="leading" constant="20" id="mzo-aG-HWn"/>
<constraint firstItem="17A-fV-wOt" firstAttribute="trailing" secondItem="urp-v1-YyC" secondAttribute="trailing" constant="20" id="zIl-fU-EGY"/>
<constraint firstItem="17A-fV-wOt" firstAttribute="trailing" secondItem="bv7-e3-oWD" secondAttribute="trailing" id="8YC-2o-N7o"/>
<constraint firstItem="bv7-e3-oWD" firstAttribute="top" secondItem="NDm-1a-rKP" secondAttribute="top" id="JAj-gP-biR"/>
<constraint firstItem="17A-fV-wOt" firstAttribute="bottom" secondItem="bv7-e3-oWD" secondAttribute="bottom" id="Kvk-eY-Qfy"/>
<constraint firstItem="bv7-e3-oWD" firstAttribute="leading" secondItem="17A-fV-wOt" secondAttribute="leading" id="YYG-CN-n6G"/>
</constraints>
<viewLayoutGuide key="safeArea" id="17A-fV-wOt"/>
</view>
<connections>
<outlet property="contentWarningTextField" destination="D8y-nm-aIL" id="ZA5-ic-drj"/>
<outlet property="replyToAvatarImageView" destination="DBM-gJ-bmW" id="JYd-Nq-rlx"/>
<outlet property="replyToContentTextView" destination="wi5-07-ou8" id="FPX-mU-jq8"/>
<outlet property="replyToDisplayNameLabel" destination="htY-mN-l6s" id="6l4-nS-0vf"/>
<outlet property="replyToUsernameLabel" destination="P5v-hq-2Ee" id="0IE-QS-d0j"/>
<outlet property="replyToView" destination="w3S-pT-NYF" id="sYr-Cy-B8b"/>
<outlet property="statusCharacterCountLabel" destination="lj5-34-gkN" id="LcE-IU-4um"/>
<outlet property="statusTextField" destination="FWE-IU-mUe" id="t2A-3P-h4d"/>
<outlet property="tootButton" destination="60a-gb-CaD" id="I50-Ix-Kuw"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Wh6-Fa-5O1" userLabel="First Responder" sceneMemberID="firstResponder"/>
<tapGestureRecognizer id="0V9-Ll-no6">
<connections>
<action selector="dismissTapped:" destination="MNp-Rq-Swn" id="twf-f6-qrO"/>
</connections>
</tapGestureRecognizer>
</objects>
<point key="canvasLocation" x="2436" y="-762.5615763546798"/>
<point key="canvasLocation" x="2701.5999999999999" y="-735.22167487684737"/>
</scene>
<!--Timeline-->
<scene sceneID="jGL-v8-K0I">
@ -1199,6 +1351,7 @@
</refreshControl>
<connections>
<segue destination="TVm-XQ-DKr" kind="presentation" identifier="TimelinesSegue" modalPresentationStyle="overCurrentContext" modalTransitionStyle="crossDissolve" id="Z24-89-f5Z"/>
<segue destination="MNp-Rq-Swn" kind="presentation" identifier="ComposeSegue" modalPresentationStyle="overCurrentContext" id="S80-S9-Doy"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="KTy-Tn-EdD" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -1224,7 +1377,7 @@
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7I8-6E-s1Q">
<rect key="frame" x="0.0" y="21" width="375" height="60"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="LHS-vw-fZE">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="LHS-vw-fZE" customClass="FLAnimatedImageView">
<rect key="frame" x="8" y="0.0" width="55" height="55"/>
<constraints>
<constraint firstAttribute="width" constant="55" id="Qmn-E0-76F"/>
@ -1543,7 +1696,7 @@
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" maximumZoomScale="6" translatesAutoresizingMaskIntoConstraints="NO" id="7ll-rV-5Xs">
<rect key="frame" x="0.0" y="83" width="375" height="514"/>
<subviews>
<imageView multipleTouchEnabled="YES" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="LVW-zt-JUe">
<imageView multipleTouchEnabled="YES" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="LVW-zt-JUe" customClass="FLAnimatedImageView">
<rect key="frame" x="0.0" y="0.0" width="375" height="514"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<gestureRecognizers/>

64
elpha-ios/ComposeViewController.swift

@ -0,0 +1,64 @@
//
// ComposeViewController.swift
// elpha-ios
//
// Created by Dwayne Harris on 11/12/18.
// Copyright © 2018 Elpha. All rights reserved.
//
import FLAnimatedImage
import UIKit
class ComposeViewController: UIViewController {
@IBOutlet var replyToView: UIView!
@IBOutlet var replyToAvatarImageView: FLAnimatedImageView!
@IBOutlet var replyToDisplayNameLabel: UILabel!
@IBOutlet var replyToUsernameLabel: UILabel!
@IBOutlet var replyToContentTextView: UITextViewFixed!
@IBOutlet var statusTextField: UITextField!
@IBOutlet var statusCharacterCountLabel: UILabel!
@IBOutlet var contentWarningTextField: UITextField!
@IBOutlet var tootButton: UIButton!
var replyToStatus: StatusMO? = nil
override func viewDidLoad() {
super.viewDidLoad()
if let replyToStatus = replyToStatus {
replyToView.isHidden = false
if let reblog = replyToStatus.reblog {
self.setupReplyTo(status: reblog)
} else {
self.setupReplyTo(status: replyToStatus)
}
} else {
replyToView.isHidden = true
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
statusTextField.becomeFirstResponder()
}
func setupReplyTo(status: StatusMO) {
if let account = status.account {
replyToAvatarImageView.loadImageURL(account.avatarURL!)
replyToDisplayNameLabel.text = account.displayName
replyToUsernameLabel.text = "@\(account.acct!)"
}
replyToContentTextView.attributedText = status.content?.htmlAttributed(size: 15.0)
}
@IBAction func statusTextFieldEdited(_ sender: Any) {
let characters = statusTextField.text!.count
statusCharacterCountLabel.text = String(500 - characters)
}
@IBAction func dismissTapped(_ sender: Any) {
dismiss(animated: true)
}
}

78
elpha-ios/FLAnimatedImageView+LoadImageURL.swift

@ -0,0 +1,78 @@
//
// FLAnimatedImage+LoadImageURL.swift
// elpha-ios
//
// Created by Dwayne Harris on 11/12/18.
// Copyright © 2018 Elpha. All rights reserved.
//
import Alamofire
import AlamofireImage
import FLAnimatedImage
import Foundation
class ImageCache {
static var cache: NSCache<AnyObject, AnyObject> = NSCache<AnyObject, AnyObject>()
static func set(string: String, data: Data) {
ImageCache.cache.setObject(data as AnyObject, forKey: string as AnyObject)
}
static func set(url: URL, data: Data) {
set(string: url.absoluteString, data: data)
}
static func set(request: URLRequest, data: Data) {
set(url: request.url!, data: data)
}
static func get(string: String) -> Data? {
if let data = ImageCache.cache.object(forKey: string as AnyObject) as? Data {
return data
} else {
return nil
}
}
static func get(url: URL) -> Data? {
return get(string: url.absoluteString)
}
static func get(request: URLRequest) -> Data? {
return get(url: request.url!)
}
}
extension FLAnimatedImageView {
func setPlaceholder() {
self.contentMode = .center
self.image = UIImage(named: "Help")
}
func loadImageURL(_ url: URL) {
if let data = ImageCache.get(url: url) {
if url.absoluteString.hasSuffix(".gif") {
self.animatedImage = FLAnimatedImage(animatedGIFData: data)
} else {
self.image = UIImage(data: data)
}
} else {
setPlaceholder()
Alamofire.request(url).responseImage { response in
if let data = response.data {
DispatchQueue.main.async {
ImageCache.set(url: url, data: data)
self.contentMode = .scaleAspectFit
if url.absoluteString.hasSuffix(".gif") {
self.animatedImage = FLAnimatedImage(animatedGIFData: data)
} else {
self.image = UIImage(data: data)
}
}
}
}
}
}
}

3
elpha-ios/MainStatusTableViewCell.swift

@ -6,10 +6,11 @@
// Copyright © 2018 Elpha. All rights reserved.
//
import FLAnimatedImage
import UIKit
class MainStatusTableViewCell: UITableViewCell {
@IBOutlet var avatarImageView: UIImageView!
@IBOutlet var avatarImageView: FLAnimatedImageView!
@IBOutlet var displayNameLabel: UILabel!
@IBOutlet var usernameLabel: UILabel!
@IBOutlet var repliesImageView: UIImageView!

13
elpha-ios/SettingsTableViewController.swift

@ -7,11 +7,12 @@
//
import AlamofireImage
import FLAnimatedImage
import UIKit
class SettingsTableViewController: UITableViewController {
@IBOutlet var headerImageView: UIImageView!
@IBOutlet var avatarImageView: UIImageView!
@IBOutlet var avatarImageView: FLAnimatedImageView!
@IBOutlet var displayNameLabel: UILabel!
@IBOutlet var usernameLabel: UILabel!
@ -20,20 +21,16 @@ class SettingsTableViewController: UITableViewController {
navigationItem.title = "Settings"
let filter = AspectScaledToFillSizeWithRoundedCornersFilter(
size: CGSize(width: 70.0, height: 70.0),
radius: 20.0,
divideRadiusByImageScale: true
)
avatarImageView.layer.shadowColor = UIColor.black.cgColor
avatarImageView.layer.shadowOpacity = 0.8
avatarImageView.layer.shadowOffset = CGSize.zero
avatarImageView.layer.shadowRadius = 10
avatarImageView.layer.cornerRadius = 10
avatarImageView.layer.masksToBounds = true
if let account = AuthenticationManager.session?.account {
headerImageView.af_setImage(withURL: account.headerURL!)
avatarImageView.af_setImage(withURL: account.avatarURL!, filter: filter)
avatarImageView.loadImageURL(account.avatarURL!)
displayNameLabel.text = account.displayName
usernameLabel.text = "@\(account.acct!)"
}

13
elpha-ios/StatusTableViewController.swift

@ -262,15 +262,16 @@ extension StatusTableViewController {
fatalError("Unable to find reusable cell")
}
let avatarFilter = AspectScaledToFillSizeWithRoundedCornersFilter(
size: CGSize(width: 40.0, height: 40.0),
radius: 30.0,
divideRadiusByImageScale: true
)
cell.avatarImageView.layer.shadowColor = UIColor.black.cgColor
cell.avatarImageView.layer.shadowOpacity = 0.8
cell.avatarImageView.layer.shadowOffset = CGSize.zero
cell.avatarImageView.layer.shadowRadius = 10
cell.avatarImageView.layer.cornerRadius = 10
cell.avatarImageView.layer.masksToBounds = true
func updateAccountView(status: StatusMO) {
if let account = status.account {
cell.avatarImageView.af_setImage(withURL: account.avatarURL!, filter: avatarFilter)
cell.avatarImageView.loadImageURL(account.avatarURL!)
cell.displayNameLabel.text = account.displayName
cell.usernameLabel.text = "@\(account.acct!)"
}

33
elpha-ios/StatusView.swift

@ -7,6 +7,7 @@
//
import AlamofireImage
import FLAnimatedImage
import UIKit
protocol StatusViewDelegate {
@ -17,6 +18,7 @@ protocol StatusViewDelegate {
func urlTapped(url: URL)
func boostTapped()
func favoriteTapped()
func replyTapped(status: StatusMO)
func revealTapped()
func hideTapped()
}
@ -25,6 +27,7 @@ extension StatusViewDelegate {
func loadMoreTapped(status: StatusMO, direction: PaginationDirection) {}
func boostTapped() {}
func favoriteTapped() {}
func replyTapped(status: StatusMO) {}
func revealTapped() {}
func hideTapped() {}
}
@ -32,14 +35,14 @@ extension StatusViewDelegate {
class StatusView: UIView {
@IBOutlet var contentView: UIView!
@IBOutlet var boostView: UIView!
@IBOutlet var boostAvatarImageView: UIImageView!
@IBOutlet var boostAvatarImageView: FLAnimatedImageView!
@IBOutlet var boostDisplayNameLabel: UILabel!
@IBOutlet var boostUsernameLabel: UILabel!
@IBOutlet var replyView: UIView!
@IBOutlet var replyAvatarImageView: UIImageView!
@IBOutlet var replyAvatarImageView: FLAnimatedImageView!
@IBOutlet var replyDisplayNameLabel: UILabel!
@IBOutlet var replyUsernameLabel: UILabel!
@IBOutlet var avatarImageView: UIImageView!
@IBOutlet var avatarImageView: FLAnimatedImageView!
@IBOutlet var displayNameLabel: UILabel!
@IBOutlet var usernameLabel: UILabel!
@IBOutlet var timestampLabel: UILabel!
@ -198,6 +201,9 @@ class StatusView: UIView {
}
}
@IBAction func replyTapped(_ sender: Any) {
delegate?.replyTapped(status: status!)
}
@objc func reveal(sender: UITapGestureRecognizer) {
if let status = status {
@ -224,6 +230,13 @@ class StatusView: UIView {
attachmentManager.delegate = self
contentTextView.delegate = self
feedbackGenerator = UINotificationFeedbackGenerator()
avatarImageView.layer.cornerRadius = 10
avatarImageView.layer.masksToBounds = true
boostAvatarImageView.layer.cornerRadius = 10
boostAvatarImageView.layer.masksToBounds = true
replyAvatarImageView.layer.cornerRadius = 10
replyAvatarImageView.layer.masksToBounds = true
}
public func update(withStatus status: StatusMO) {
@ -235,15 +248,9 @@ class StatusView: UIView {
attachmentsView.isHidden = true
spoilerImageView.isHidden = true
let avatarFilter = AspectScaledToFillSizeWithRoundedCornersFilter(
size: CGSize(width: 40.0, height: 40.0),
radius: 20.0,
divideRadiusByImageScale: true
)
func updateStatusContent(_ status: StatusMO) {
if let account = status.account {
avatarImageView.af_setImage(withURL: account.avatarURL!, filter: avatarFilter)
avatarImageView.loadImageURL(account.avatarURL!)
displayNameLabel.text = account.displayName
usernameLabel.text = "@\(account.acct!)"
}
@ -326,7 +333,7 @@ class StatusView: UIView {
boostView.isHidden = false
if let account = status.account {
boostAvatarImageView.af_setImage(withURL: account.avatarURL!, filter: avatarFilter)
boostAvatarImageView.loadImageURL(account.avatarURL!)
boostDisplayNameLabel.text = account.displayName
boostUsernameLabel.text = "@\(account.acct!)"
}
@ -334,7 +341,7 @@ class StatusView: UIView {
updateStatusContent(reblog)
} else {
if let account = status.account {
avatarImageView.af_setImage(withURL: account.avatarURL!, filter: avatarFilter)
avatarImageView.loadImageURL(account.avatarURL!)
displayNameLabel.text = account.displayName
usernameLabel.text = "@\(account.acct!)"
}
@ -345,7 +352,7 @@ class StatusView: UIView {
if let replyAccountID = status.inReplyToAccountID {
if let replyAccount = MastodonDataManager.account(id: replyAccountID) {
replyView.isHidden = false
replyAvatarImageView.af_setImage(withURL: replyAccount.avatarURL!, filter: avatarFilter)
replyAvatarImageView.loadImageURL(replyAccount.avatarURL!)
replyDisplayNameLabel.text = replyAccount.displayName
replyUsernameLabel.text = "@\(replyAccount.acct!)"
}

15
elpha-ios/StatusView.xib

@ -91,7 +91,7 @@
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Gnt-jG-tVj">
<rect key="frame" x="0.0" y="55" width="375" height="100"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="ecc-6t-6K0">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="ecc-6t-6K0" customClass="FLAnimatedImageView">
<rect key="frame" x="8" y="20" width="40" height="40"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="IbJ-fQ-bcS"/>
@ -159,7 +159,7 @@
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="v8x-tf-zFb">
<rect key="frame" x="0.0" y="155" width="375" height="100"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="wte-HK-KxR">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="wte-HK-KxR" customClass="FLAnimatedImageView">
<rect key="frame" x="8" y="20" width="40" height="40"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="LeJ-eE-S7V"/>
@ -230,7 +230,7 @@
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="50m-cW-QIF">
<rect key="frame" x="0.0" y="8" width="367" height="70"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="i10-Gq-NR9">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="i10-Gq-NR9" customClass="FLAnimatedImageView">
<rect key="frame" x="8" y="10" width="45" height="45"/>
<constraints>
<constraint firstAttribute="height" constant="45" id="PGv-cY-D3o"/>
@ -430,6 +430,7 @@
</imageView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<gestureRecognizers/>
<constraints>
<constraint firstItem="Uog-Tq-elS" firstAttribute="leading" secondItem="mRG-vF-1Ur" secondAttribute="trailing" constant="8" id="H6T-dO-Miu"/>
<constraint firstAttribute="width" constant="60" id="TZY-d9-Lgc"/>
@ -438,6 +439,9 @@
<constraint firstAttribute="height" constant="30" id="vKV-p4-UOJ"/>
<constraint firstItem="mRG-vF-1Ur" firstAttribute="centerY" secondItem="ZKM-hs-NVu" secondAttribute="centerY" id="xMG-H7-qbH"/>
</constraints>
<connections>
<outletCollection property="gestureRecognizers" destination="OKt-NI-g93" appends="YES" id="PG5-Es-x6l"/>
</connections>
</view>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@ -556,6 +560,11 @@
<action selector="favoriteTapped:" destination="-1" id="Hs1-BT-9gH"/>
</connections>
</tapGestureRecognizer>
<tapGestureRecognizer id="OKt-NI-g93">
<connections>
<action selector="replyTapped:" destination="-1" id="fO5-jo-Y2d"/>
</connections>
</tapGestureRecognizer>
</objects>
<resources>
<image name="Alert" width="22" height="22"/>

22
elpha-ios/TimelineTableViewController.swift

@ -16,6 +16,8 @@ class TimelineTableViewController: AbstractStatusTableViewController {
var feedbackGenerator: UINotificationFeedbackGenerator? = nil
var fetchedResultsController: NSFetchedResultsController<StatusMO>? = nil
var replyToStatus: StatusMO? = nil
override var currentPaginationContext: String {
get {
guard let timeline = AuthenticationManager.session?.timeline, let categoryString = timeline.category else {
@ -99,6 +101,11 @@ class TimelineTableViewController: AbstractStatusTableViewController {
}
}
override func replyTapped(status: StatusMO) {
replyToStatus = status
performSegue(withIdentifier: "ComposeSegue", sender: self)
}
@objc func openSettings() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
@ -111,16 +118,23 @@ class TimelineTableViewController: AbstractStatusTableViewController {
performSegue(withIdentifier: "TimelinesSegue", sender: self)
}
@objc func compose() {
replyToStatus = nil
performSegue(withIdentifier: "ComposeSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "TimelinesSegue" {
if let destination = segue.destination as? TimelinesViewController {
destination.delegate = self
}
}
}
@objc func compose() {
AlertManager.shared.show(message: "We'll get the compose screen working one day.")
if segue.identifier == "ComposeSegue" {
if let destination = segue.destination as? ComposeViewController {
destination.replyToStatus = replyToStatus
}
}
}
func createDefaultTimelines(account: AccountMO) {

Loading…
Cancel
Save