Browse Source

Development

master
Dwayne Harris 6 years ago
parent
commit
c71c4e893e
  1. 8
      elpha-ios.xcodeproj/project.pbxproj
  2. 13
      elpha-ios/AccountTableViewCell.swift
  3. 180
      elpha-ios/AccountTableViewController.swift
  4. 13
      elpha-ios/AlertManager.swift
  5. 10
      elpha-ios/AttachmentsManager.swift
  6. 42
      elpha-ios/Base.lproj/Main.storyboard
  7. 34
      elpha-ios/Elpha.xcdatamodeld/Elpha.xcdatamodel/contents
  8. 4
      elpha-ios/InstancesDataManager.swift
  9. 6
      elpha-ios/InstancesTableViewController.swift
  10. 5
      elpha-ios/MastodonDataManager.swift
  11. 40
      elpha-ios/StatusView.swift
  12. 197
      elpha-ios/StatusView.xib
  13. 210
      elpha-ios/TimelineTableViewController.swift
  14. 37
      elpha-ios/TimelinesNavigationController.swift

8
elpha-ios.xcodeproj/project.pbxproj

@ -17,6 +17,8 @@
151AD4E1216899F900F07403 /* MastodonKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15A79B02215B3CC5007A326E /* MastodonKit.framework */; };
151AD4E2216899F900F07403 /* MastodonKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 15A79B02215B3CC5007A326E /* MastodonKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
151AD4E621689A0F00F07403 /* Alamofire.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 157405C3215890BC00EEAAEB /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
156FF015217289380074D9CA /* AccountTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 156FF014217289380074D9CA /* AccountTableViewCell.swift */; };
156FF02F217309480074D9CA /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 156FF02E217309480074D9CA /* AlertManager.swift */; };
157405A82150588A00EEAAEB /* InstanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 157405A72150588A00EEAAEB /* InstanceViewController.swift */; };
157405B12151A5DA00EEAAEB /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 157405AF2151A5DA00EEAAEB /* README.md */; };
157405B42151A93E00EEAAEB /* InstancesDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 157405B32151A93E00EEAAEB /* InstancesDataManager.swift */; };
@ -285,6 +287,8 @@
15131EF3216DB8B90092B252 /* AccountTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTableViewController.swift; sourceTree = "<group>"; };
15131EF5216DBA820092B252 /* AccountNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountNavigationController.swift; sourceTree = "<group>"; };
1517EA6F2159D72200DE80D6 /* AlamofireImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = AlamofireImage.xcodeproj; path = Frameworks/AlamofireImage/AlamofireImage.xcodeproj; sourceTree = "<group>"; };
156FF014217289380074D9CA /* AccountTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTableViewCell.swift; sourceTree = "<group>"; };
156FF02E217309480074D9CA /* AlertManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertManager.swift; sourceTree = "<group>"; };
157405A72150588A00EEAAEB /* InstanceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceViewController.swift; sourceTree = "<group>"; };
157405AF2151A5DA00EEAAEB /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
157405B32151A93E00EEAAEB /* InstancesDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesDataManager.swift; sourceTree = "<group>"; };
@ -362,6 +366,7 @@
151AD4AC2166DD0200F07403 /* Managers */ = {
isa = PBXGroup;
children = (
156FF02E217309480074D9CA /* AlertManager.swift */,
1574148C2169AD0100C841BD /* AttachmentsManager.swift */,
15960E7B213272CD00C38CE9 /* AuthenticationManager.swift */,
15A79B42215EB959007A326E /* CoreDataManager.swift */,
@ -385,6 +390,7 @@
151AD4AE2166DD3500F07403 /* Views */ = {
isa = PBXGroup;
children = (
156FF014217289380074D9CA /* AccountTableViewCell.swift */,
159048AE214F5015004F4014 /* InstancesTableViewCell.swift */,
15BB72AA2171A8D4002F1FA4 /* TimelinesTableViewCell.swift */,
159026AD2162CF5600D362DD /* TimelineTableViewCell.swift */,
@ -808,6 +814,7 @@
15960E7C213272CD00C38CE9 /* AuthenticationManager.swift in Sources */,
15960E7E21329FED00C38CE9 /* AuthenticateViewController.swift in Sources */,
15960E5B213145E100C38CE9 /* AppDelegate.swift in Sources */,
156FF02F217309480074D9CA /* AlertManager.swift in Sources */,
15131EF2216D8D570092B252 /* StatusView.swift in Sources */,
15BB72A92171A6BE002F1FA4 /* TimelinesTableViewController.swift in Sources */,
15960E7721322C6F00C38CE9 /* Configuration.swift in Sources */,
@ -820,6 +827,7 @@
15A79B43215EB959007A326E /* CoreDataManager.swift in Sources */,
15BB72AB2171A8D4002F1FA4 /* TimelinesTableViewCell.swift in Sources */,
1574148D2169AD0100C841BD /* AttachmentsManager.swift in Sources */,
156FF015217289380074D9CA /* AccountTableViewCell.swift in Sources */,
15960E822136668500C38CE9 /* TimelinesNavigationController.swift in Sources */,
159026AE2162CF5600D362DD /* TimelineTableViewCell.swift in Sources */,
);

13
elpha-ios/AccountTableViewCell.swift

@ -0,0 +1,13 @@
//
// AccountTableViewCell.swift
// elpha-ios
//
// Created by Dwayne Harris on 10/13/18.
// Copyright © 2018 Elpha. All rights reserved.
//
import UIKit
class AccountTableViewCell: UITableViewCell {
@IBOutlet var statusView: StatusView!
}

180
elpha-ios/AccountTableViewController.swift

@ -7,6 +7,7 @@
//
import AlamofireImage
import CoreData
import MastodonKit
import UIKit
@ -20,10 +21,34 @@ class AccountTableViewController: UITableViewController {
@IBOutlet var statusesLabel: UILabel!
@IBOutlet var followingLabel: UILabel!
@IBOutlet var followersLabel: UILabel!
@IBOutlet var statusTypeSegmentedControl: UISegmentedControl!
public var account: AccountMO? = nil
let fetchLimit = 20
var fetchedResultsController: NSFetchedResultsController<StatusMO>? = nil
var account: AccountMO? = nil
var loading: Bool = false {
didSet {
DispatchQueue.main.async {
if self.loading {
self.refreshControl?.beginRefreshing()
} else {
self.refreshControl?.endRefreshing()
}
}
}
}
@IBAction func statusTypeChanged(_ sender: UISegmentedControl) {
initializeFetchedResultsController()
fetch()
}
private func updateHeader(withAccount account: AccountMO) {
navigationItem.title = account.displayName
let avatarFilter = AspectScaledToFillSizeWithRoundedCornersFilter(
size: CGSize(width: 70.0, height: 70.0),
radius: 20.0,
@ -64,6 +89,8 @@ class AccountTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
refreshControl?.addTarget(self, action: #selector(self.fetch), for: .valueChanged)
if self.account == nil {
if let session = AuthenticationManager.shared.session {
self.account = session.account
@ -72,6 +99,13 @@ class AccountTableViewController: UITableViewController {
if let account = account {
updateHeader(withAccount: account)
initializeFetchedResultsController()
}
if let session = AuthenticationManager.shared.session {
if session.account == account {
statusTypeSegmentedControl.insertSegment(withTitle: "Favorites", at: statusTypeSegmentedControl.numberOfSegments, animated: true)
}
}
}
@ -90,6 +124,7 @@ class AccountTableViewController: UITableViewController {
if let account = self.account {
DispatchQueue.main.async {
self.updateHeader(withAccount: account)
self.fetch()
}
}
case .failure(let error):
@ -99,4 +134,147 @@ class AccountTableViewController: UITableViewController {
}
}
}
@objc func fetch() {
fetchStatuses(withRange: .limit(fetchLimit)) { statuses, error in
if error != nil {
print("\(String(describing: error))")
}
}
}
func fetchStatuses(withRange requestRange: RequestRange, completion: @escaping ([UpsertResult<StatusMO>], Error?) -> Void) {
guard let client = AuthenticationManager.shared.mkClient else {
completion([], nil)
return
}
if let account = account {
var request: Request<[Status]>
switch statusTypeSegmentedControl.selectedSegmentIndex {
case 1:
request = Accounts.statuses(id: account.id!, mediaOnly: false, pinnedOnly: false, excludeReplies: false, range: requestRange)
case 2:
request = Accounts.statuses(id: account.id!, mediaOnly: true, pinnedOnly: false, excludeReplies: true, range: requestRange)
case 3:
request = Favourites.all(range: requestRange)
default:
request = Accounts.statuses(id: account.id!, mediaOnly: false, pinnedOnly: false, excludeReplies: true, range: requestRange)
}
client.run(request) { result in
switch result {
case .success(let remoteStatuses, _):
DispatchQueue.main.async {
let statuses = remoteStatuses.compactMap { status in
return MastodonDataManager.upsertStatus(status)
}
CoreDataManager.shared.saveContext()
self.loading = false
completion(statuses, nil)
}
case .failure(let error):
completion([], error)
}
}
}
}
}
extension AccountTableViewController: NSFetchedResultsControllerDelegate {
func initializeFetchedResultsController() {
if let account = self.account {
let request = NSFetchRequest<StatusMO>(entityName: "Status")
request.sortDescriptors = [
NSSortDescriptor(key: "pinned", ascending: true),
NSSortDescriptor(key: "createdAt", ascending: false),
]
switch statusTypeSegmentedControl.selectedSegmentIndex {
case 1:
request.predicate = NSPredicate(format: "account == %@", account)
case 2:
request.predicate = NSPredicate(format: "account == %@ AND attachments.@count > 0", account)
case 3:
request.predicate = NSPredicate(format: "favourited == YES")
default:
request.predicate = NSPredicate(format: "account == %@ AND inReplyToID == nil", account)
}
fetchedResultsController = NSFetchedResultsController(
fetchRequest: request,
managedObjectContext: CoreDataManager.shared.context,
sectionNameKeyPath: nil,
cacheName: nil
)
fetchedResultsController!.delegate = self
do {
try fetchedResultsController!.performFetch()
} catch {
print("\(error)")
}
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.reloadData()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let count = fetchedResultsController?.fetchedObjects?.count else {
return 0
}
return count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "AccountTableViewCell", for: indexPath) as? AccountTableViewCell else {
fatalError("Unable to find reusable cell")
}
guard let status = fetchedResultsController?.object(at: indexPath) else {
fatalError("CoreData error")
}
cell.statusView.delegate = self
cell.statusView.update(withStatus: status)
if !loading {
let statusAge = Calendar.current.dateComponents([.minute], from: status.fetchedAt!, to: Date())
let stale = statusAge.minute! > 30
let first = indexPath.row == 0
let last = indexPath.row == fetchedResultsController?.fetchedObjects?.count ?? 0 - 1
if (!first && stale) || last {
fetchStatuses(withRange: .max(id: status.id!, limit: fetchLimit)) { statuses, error in
if error != nil {
print("\(String(describing: error))")
}
}
}
}
return cell
}
}
extension AccountTableViewController: StatusViewDelegate {
func accountTapped(account: AccountMO) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let controller = storyboard.instantiateViewController(withIdentifier: "AccountTableViewController") as? AccountTableViewController {
controller.account = account
self.navigationController?.pushViewController(controller, animated: true)
}
}
}

13
elpha-ios/AlertManager.swift

@ -0,0 +1,13 @@
//
// AlertManager.swift
// elpha-ios
//
// Created by Dwayne Harris on 10/13/18.
// Copyright © 2018 Elpha. All rights reserved.
//
import Foundation
class AlertManager {
}

10
elpha-ios/AttachmentsManager.swift

@ -119,8 +119,8 @@ class AttachmentsManager {
}
NSLayoutConstraint.activate([
imageViews[2].topAnchor.constraint(equalTo: view.topAnchor),
imageViews[3].bottomAnchor.constraint(equalTo: view.bottomAnchor),
imageViews[1].topAnchor.constraint(equalTo: view.topAnchor),
imageViews[2].bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
default:
let filter = AspectScaledToFillSizeFilter(size: CGSize(width: halfWidth, height: halfWidth))
@ -154,10 +154,10 @@ class AttachmentsManager {
imageViews[0].leadingAnchor.constraint(equalTo: view.leadingAnchor),
imageViews[1].topAnchor.constraint(equalTo: view.topAnchor),
imageViews[1].trailingAnchor.constraint(equalTo: view.trailingAnchor),
imageViews[3].leadingAnchor.constraint(equalTo: view.leadingAnchor),
imageViews[2].leadingAnchor.constraint(equalTo: view.leadingAnchor),
imageViews[2].bottomAnchor.constraint(equalTo: view.bottomAnchor),
imageViews[3].bottomAnchor.constraint(equalTo: view.bottomAnchor),
imageViews[4].bottomAnchor.constraint(equalTo: view.bottomAnchor),
imageViews[4].trailingAnchor.constraint(equalTo: view.trailingAnchor),
imageViews[3].trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
}
}

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

@ -483,7 +483,7 @@
<!--Account-->
<scene sceneID="mCa-NC-tD9">
<objects>
<tableViewController id="X5f-Me-0D2" customClass="AccountTableViewController" customModule="elpha_ios" customModuleProvider="target" sceneMemberID="viewController">
<tableViewController storyboardIdentifier="AccountTableViewController" id="X5f-Me-0D2" customClass="AccountTableViewController" customModule="elpha_ios" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="none" allowsSelection="NO" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="BA3-TV-fdC">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -612,6 +612,9 @@
<segment title="Media"/>
</segments>
<color key="tintColor" red="0.72941176470000002" green="0.6705882353" blue="0.83921568629999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<action selector="statusTypeChanged:" destination="X5f-Me-0D2" eventType="valueChanged" id="9ew-Jh-vG0"/>
</connections>
</segmentedControl>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@ -647,13 +650,28 @@
</constraints>
</view>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="AccountTableViewCell" id="nLQ-4Q-SwI">
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="AccountTableViewCell" id="nLQ-4Q-SwI" customClass="AccountTableViewCell" customModule="elpha_ios" customModuleProvider="target">
<rect key="frame" x="0.0" y="517" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="nLQ-4Q-SwI" id="y0d-Ka-W2g">
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XFR-lZ-fvp" customClass="StatusView" customModule="elpha_ios" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<constraints>
<constraint firstItem="XFR-lZ-fvp" firstAttribute="top" secondItem="y0d-Ka-W2g" secondAttribute="top" id="MRa-bX-gz3"/>
<constraint firstAttribute="bottom" secondItem="XFR-lZ-fvp" secondAttribute="bottom" id="Q5i-CR-EZc"/>
<constraint firstItem="XFR-lZ-fvp" firstAttribute="leading" secondItem="y0d-Ka-W2g" secondAttribute="leading" id="Tfe-6f-ZGb"/>
<constraint firstAttribute="trailing" secondItem="XFR-lZ-fvp" secondAttribute="trailing" id="vPz-1r-sFi"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="statusView" destination="XFR-lZ-fvp" id="23V-dA-eR7"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
@ -662,6 +680,17 @@
</connections>
</tableView>
<navigationItem key="navigationItem" title="Account" id="5hW-Ep-t5f"/>
<refreshControl key="refreshControl" opaque="NO" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" id="fRb-ea-Rvd">
<autoresizingMask key="autoresizingMask"/>
<attributedString key="attributedTitle">
<fragment content="Loading ...">
<attributes>
<font key="NSFont" size="13" name=".AppleSystemUIFont"/>
<paragraphStyle key="NSParagraphStyle" alignment="center" lineBreakMode="wordWrapping" baseWritingDirection="natural" tighteningFactorForTruncation="0.0"/>
</attributes>
</fragment>
</attributedString>
</refreshControl>
<connections>
<outlet property="avatarImageView" destination="d7X-u9-Seq" id="Mt2-60-lb2"/>
<outlet property="contentLabel" destination="d8Z-vZ-agH" id="e0a-ZE-IdC"/>
@ -670,6 +699,7 @@
<outlet property="followingLabel" destination="l3U-wO-M4g" id="N5A-sA-0O1"/>
<outlet property="headerImageView" destination="0jO-Rb-lcz" id="30G-8E-1xD"/>
<outlet property="headerView" destination="ygB-F6-Moa" id="wXs-JO-7oD"/>
<outlet property="statusTypeSegmentedControl" destination="lwU-IL-uAN" id="LVa-A5-EGt"/>
<outlet property="statusesLabel" destination="Eab-8s-Drh" id="zTr-ZY-uHR"/>
<outlet property="usernameLabel" destination="XET-LP-bGO" id="ffw-On-bkz"/>
</connections>
@ -776,12 +806,12 @@
<outlet property="delegate" destination="ohq-uu-dqB" id="rXh-e9-NRO"/>
</connections>
</tableView>
<value key="contentSizeForViewInPopover" type="size" width="300" height="300"/>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="e9T-Uk-D5X" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1634.7826086956522" y="-1043.3035714285713"/>
<point key="canvasLocation" x="2394" y="-1052"/>
</scene>
<!--Timeline-->
<scene sceneID="jGL-v8-K0I">
@ -828,7 +858,9 @@
<attributedString key="attributedTitle"/>
</refreshControl>
<connections>
<segue destination="ohq-uu-dqB" kind="presentation" identifier="TimelinesSegue" modalPresentationStyle="overCurrentContext" id="lfm-EQ-h2m"/>
<segue destination="ohq-uu-dqB" kind="popoverPresentation" identifier="TimelinesSegue" popoverAnchorView="fgT-hp-jpg" id="lfm-EQ-h2m">
<popoverArrowDirection key="popoverArrowDirection" up="YES" down="YES" left="YES" right="YES"/>
</segue>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="KTy-Tn-EdD" userLabel="First Responder" sceneMemberID="firstResponder"/>

34
elpha-ios/Elpha.xcdatamodeld/Elpha.xcdatamodel/contents

@ -7,6 +7,7 @@
<attribute name="bot" optional="YES" attributeType="Boolean" usesScalarValueType="YES" syncable="YES"/>
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="displayName" attributeType="String" syncable="YES"/>
<attribute name="fetchedAt" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="followersCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="followingCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="headerStaticURL" optional="YES" attributeType="URI" syncable="YES"/>
@ -23,11 +24,13 @@
<relationship name="timelines" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Timeline" inverseName="account" inverseEntity="Timeline" syncable="YES"/>
</entity>
<entity name="App" representedClassName="AppMO" syncable="YES" codeGenerationType="class">
<attribute name="fetchedAt" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="website" optional="YES" attributeType="URI" syncable="YES"/>
<relationship name="statuses" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="app" inverseEntity="Status" syncable="YES"/>
</entity>
<entity name="Attachment" representedClassName="AttachmentMO" syncable="YES" codeGenerationType="class">
<attribute name="fetchedAt" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="previewURL" attributeType="URI" syncable="YES"/>
<attribute name="remoteURL" optional="YES" attributeType="String" syncable="YES"/>
@ -39,15 +42,18 @@
<entity name="Client" representedClassName="ClientMO" syncable="YES" codeGenerationType="class">
<attribute name="clientID" attributeType="String" syncable="YES"/>
<attribute name="clientSecret" attributeType="String" syncable="YES"/>
<attribute name="fetchedAt" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="url" attributeType="String" syncable="YES"/>
<relationship name="sessions" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Session" inverseName="client" inverseEntity="Session" syncable="YES"/>
</entity>
<entity name="Emoji" representedClassName="EmojiMO" syncable="YES" codeGenerationType="class">
<attribute name="fetchedAt" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="shortcode" attributeType="String" syncable="YES"/>
<attribute name="staticURL" attributeType="URI" syncable="YES"/>
<attribute name="url" attributeType="URI" syncable="YES"/>
<attribute name="visibleInPicker" attributeType="Boolean" usesScalarValueType="YES" syncable="YES"/>
<relationship name="statuses" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="emojis" inverseEntity="Status" syncable="YES"/>
</entity>
<entity name="ISCategory" representedClassName="ISCategoryMO" syncable="YES" codeGenerationType="class">
<attribute name="id" attributeType="String" syncable="YES"/>
@ -97,6 +103,7 @@
</entity>
<entity name="Mention" representedClassName="MentionMO" syncable="YES" codeGenerationType="class">
<attribute name="acct" attributeType="String" syncable="YES"/>
<attribute name="fetchedAt" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="url" attributeType="URI" syncable="YES"/>
<attribute name="username" attributeType="String" syncable="YES"/>
@ -116,6 +123,7 @@
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="favourited" optional="YES" attributeType="Boolean" usesScalarValueType="YES" syncable="YES"/>
<attribute name="favouritesCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="fetchedAt" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="inReplyToAccountID" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="inReplyToID" optional="YES" attributeType="String" syncable="YES"/>
@ -130,7 +138,7 @@
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Account" inverseName="statuses" inverseEntity="Account" syncable="YES"/>
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="App" inverseName="statuses" inverseEntity="App" syncable="YES"/>
<relationship name="attachments" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="Attachment" inverseName="status" inverseEntity="Attachment" syncable="YES"/>
<relationship name="boundaries" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="TimelineBoundary" inverseName="status" inverseEntity="TimelineBoundary" syncable="YES"/>
<relationship name="emojis" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Emoji" inverseName="statuses" inverseEntity="Emoji" syncable="YES"/>
<relationship name="mentions" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Mention" inverseName="status" inverseEntity="Mention" syncable="YES"/>
<relationship name="reblog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="reblogs" inverseEntity="Status" syncable="YES"/>
<relationship name="reblogs" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="reblog" inverseEntity="Status" syncable="YES"/>
@ -146,30 +154,22 @@
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="order" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Account" inverseName="timelines" inverseEntity="Account" syncable="YES"/>
<relationship name="boundaries" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="TimelineBoundary" inverseName="timeline" inverseEntity="TimelineBoundary" syncable="YES"/>
<relationship name="session" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Session" inverseName="timeline" inverseEntity="Session" syncable="YES"/>
<relationship name="statuses" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="timelines" inverseEntity="Status" syncable="YES"/>
</entity>
<entity name="TimelineBoundary" representedClassName="TimelineBoundaryMO" syncable="YES" codeGenerationType="class">
<attribute name="fetchedAt" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="start" attributeType="Boolean" usesScalarValueType="YES" syncable="YES"/>
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="boundaries" inverseEntity="Status" syncable="YES"/>
<relationship name="timeline" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Timeline" inverseName="boundaries" inverseEntity="Timeline" syncable="YES"/>
</entity>
<elements>
<element name="Account" positionX="-669.49609375" positionY="52.046875" width="128" height="345"/>
<element name="App" positionX="-423" positionY="252" width="128" height="90"/>
<element name="Attachment" positionX="-450" positionY="225" width="128" height="150"/>
<element name="Client" positionX="-244.4140625" positionY="307.203125" width="128" height="120"/>
<element name="Account" positionX="-669.49609375" positionY="52.046875" width="128" height="360"/>
<element name="App" positionX="-423" positionY="252" width="128" height="105"/>
<element name="Attachment" positionX="-450" positionY="225" width="128" height="165"/>
<element name="Client" positionX="-244.4140625" positionY="307.203125" width="128" height="135"/>
<element name="Emoji" positionX="-468" positionY="207" width="128" height="135"/>
<element name="ISCategory" positionX="196.8984375" positionY="498.03515625" width="128" height="75"/>
<element name="ISInstance" positionX="-18" positionY="153" width="128" height="435"/>
<element name="ISLanguage" positionX="-286.21875" positionY="512.6171875" width="128" height="75"/>
<element name="Mention" positionX="-441" positionY="234" width="128" height="120"/>
<element name="Mention" positionX="-441" positionY="234" width="128" height="135"/>
<element name="Session" positionX="-445.046875" positionY="277.31640625" width="128" height="150"/>
<element name="Status" positionX="-459" positionY="216" width="128" height="405"/>
<element name="Status" positionX="-459" positionY="216" width="128" height="420"/>
<element name="Tag" positionX="-432" positionY="243" width="128" height="90"/>
<element name="Timeline" positionX="-468" positionY="207" width="128" height="135"/>
<element name="TimelineBoundary" positionX="-468" positionY="207" width="128" height="105"/>
<element name="Emoji" positionX="-468" positionY="207" width="128" height="105"/>
<element name="Timeline" positionX="-468" positionY="207" width="128" height="120"/>
</elements>
</model>

4
elpha-ios/InstancesDataManager.swift

@ -13,7 +13,7 @@ import UIKit
class InstancesDataManager {
static let shared = InstancesDataManager()
let instanceRequestCount = 20
let fetchLimit = 20
var instances: [ISInstanceMO] = []
var nextID: String? = nil
var finished = false
@ -144,7 +144,7 @@ class InstancesDataManager {
func loadInstances(completion: @escaping (Error?) -> Void) {
var params = [
"count=\(instanceRequestCount)",
"count=\(fetchLimit)",
"sort_by=users",
"sort_order=desc",
"include_closed=true",

6
elpha-ios/InstancesTableViewController.swift

@ -12,6 +12,8 @@ import UIKit
class InstancesTableViewController: UITableViewController, UIViewControllerPreviewingDelegate, NSFetchedResultsControllerDelegate {
@IBOutlet var mainNavigationItem: UINavigationItem!
let fetchLimit = 20
var fetchedResultsController: NSFetchedResultsController<ISInstanceMO>? = nil
var loading: Bool = false {
@ -113,8 +115,6 @@ class InstancesTableViewController: UITableViewController, UIViewControllerPrevi
guard let instance = fetchedResultsController?.object(at: indexPath) else {
fatalError("CoreData error")
}
let instanceCount = tableView.numberOfRows(inSection: 0)
cell.instanceNameLabel.text = instance.name
cell.statusesLabel.text = NumberFormatter.localizedString(from: NSNumber(value: instance.statuses), number: .decimal)
@ -124,7 +124,7 @@ class InstancesTableViewController: UITableViewController, UIViewControllerPrevi
cell.thumbnailImageView.af_setImage(withURL: thumbnail)
}
if indexPath.row == instanceCount - 2 && !loading && !InstancesDataManager.shared.finished {
if indexPath.row > 0 && indexPath.row % fetchLimit == 0 && !loading {
fetchInstances()
}

5
elpha-ios/MastodonDataManager.swift

@ -37,6 +37,7 @@ public class MastodonDataManager {
account.followersCount = Int32(remoteAccount.followersCount)
account.followingCount = Int32(remoteAccount.followingCount)
account.statusesCount = Int32(remoteAccount.statusesCount)
account.fetchedAt = Date()
return account
}
@ -65,6 +66,7 @@ public class MastodonDataManager {
attachment.previewURL = URL(string: remoteAttachment.previewURL)
attachment.remoteURL = remoteAttachment.remoteURL
attachment.textURL = remoteAttachment.textURL
attachment.fetchedAt = Date()
return attachment
}
@ -91,6 +93,7 @@ public class MastodonDataManager {
mention.username = remoteMention.username
mention.acct = remoteMention.acct
mention.url = URL(string: remoteMention.url)
mention.fetchedAt = Date()
return mention
}
@ -168,6 +171,7 @@ public class MastodonDataManager {
emoji.staticURL = remoteEmoji.staticURL
emoji.url = remoteEmoji.url
emoji.visibleInPicker = false
emoji.fetchedAt = Date()
return emoji
}
@ -206,6 +210,7 @@ public class MastodonDataManager {
status.pinned = remoteStatus.pinned ?? false
status.spoilerText = remoteStatus.spoilerText
status.visibility = remoteStatus.visibility.rawValue
status.fetchedAt = Date()
if let app = remoteStatus.application {
status.app = MastodonDataManager.upsertApp(app)

40
elpha-ios/StatusView.swift

@ -9,6 +9,10 @@
import AlamofireImage
import UIKit
protocol StatusViewDelegate {
func accountTapped(account: AccountMO)
}
class StatusView: UIView {
@IBOutlet var contentView: UIView!
@IBOutlet var boostView: UIView!
@ -31,12 +35,38 @@ class StatusView: UIView {
@IBOutlet var favoritesImageView: UIImageView!
@IBOutlet var favoritesLabel: UILabel!
@IBOutlet var topDividerView: UIView!
@IBOutlet var topLoadMoreView: UIView!
@IBOutlet var bottomDividerView: UIView!
@IBOutlet var bottomLoadMoreView: UIView!
@IBOutlet var attachmentsView: UIView!
@IBOutlet var attachmentsHeightConstraint: NSLayoutConstraint!
var status: StatusMO? = nil
var delegate: StatusViewDelegate? = nil
@IBAction func boostViewTapped(_ sender: Any) {
if let delegate = delegate, let status = status {
delegate.accountTapped(account: status.account!)
}
}
@IBAction func replyViewTapped(_ sender: Any) {
if let delegate = delegate,
let status = status,
let replyAccountID = status.inReplyToAccountID,
let replyAccount = MastodonDataManager.accountByID(replyAccountID) {
delegate.accountTapped(account: replyAccount)
}
}
@IBAction func accountViewTapped(_ sender: Any) {
if let delegate = delegate, let status = status {
if let reblog = status.reblog {
delegate.accountTapped(account: reblog.account!)
} else {
delegate.accountTapped(account: status.account!)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
@ -51,15 +81,15 @@ class StatusView: UIView {
Bundle.main.loadNibNamed("StatusView", owner: self, options: nil)
addSubview(contentView)
contentView.frame = self.bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
public func update(withStatus status: StatusMO) {
self.status = status
topDividerView.isHidden = false
topLoadMoreView.isHidden = true
boostView.isHidden = true
replyView.isHidden = true
bottomLoadMoreView.isHidden = true
bottomDividerView.isHidden = false
attachmentsView.backgroundColor = UIColor.white

197
elpha-ios/StatusView.xib

@ -13,7 +13,7 @@
<connections>
<outlet property="attachmentsHeightConstraint" destination="nbr-i2-Chb" id="osy-bg-2Ny"/>
<outlet property="attachmentsView" destination="XuS-iF-YYL" id="HFB-cz-e3h"/>
<outlet property="avatarImageView" destination="TG6-9M-llp" id="01I-Lj-LAR"/>
<outlet property="avatarImageView" destination="i10-Gq-NR9" id="xe9-ci-dSr"/>
<outlet property="boostAvatarImageView" destination="ecc-6t-6K0" id="Z09-BM-AEA"/>
<outlet property="boostDisplayNameLabel" destination="kjf-lJ-8Md" id="bR3-bE-zsa"/>
<outlet property="boostUsernameLabel" destination="PKb-3W-PjE" id="QvB-ol-0mf"/>
@ -21,10 +21,9 @@
<outlet property="boostsImageView" destination="wns-8Z-i60" id="pPf-qQ-T2A"/>
<outlet property="boostsLabel" destination="rq6-p2-tL4" id="Osw-F4-eLa"/>
<outlet property="bottomDividerView" destination="m4t-ve-E78" id="fEE-Uh-2qq"/>
<outlet property="bottomLoadMoreView" destination="QYs-Yp-yiP" id="yld-0E-FMm"/>
<outlet property="contentLabel" destination="WPF-Oe-N2r" id="Gw6-2e-SDE"/>
<outlet property="contentView" destination="iN0-l3-epB" id="BBM-O7-PHb"/>
<outlet property="displayNameLabel" destination="Tdt-bM-8kr" id="bgA-t1-MWi"/>
<outlet property="displayNameLabel" destination="a9Z-2P-cZT" id="9w7-9G-cp3"/>
<outlet property="favoritesImageView" destination="EID-tt-v32" id="nxH-Fm-sb8"/>
<outlet property="favoritesLabel" destination="MHw-QN-ZwH" id="cNY-nH-fIO"/>
<outlet property="repliesImageView" destination="mRG-vF-1Ur" id="zW6-NU-fvW"/>
@ -35,8 +34,7 @@
<outlet property="replyView" destination="v8x-tf-zFb" id="MkC-Dz-5Cw"/>
<outlet property="timestampLabel" destination="8et-mR-yd2" id="VW6-9b-k6T"/>
<outlet property="topDividerView" destination="bg1-Q4-Ru5" id="hCA-Qw-9OZ"/>
<outlet property="topLoadMoreView" destination="Xil-Cx-e0H" id="K1N-Ka-znn"/>
<outlet property="usernameLabel" destination="OR8-xh-j0I" id="Bq4-9u-fCu"/>
<outlet property="usernameLabel" destination="1xI-cX-lcs" id="IaM-0W-3dg"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
@ -54,48 +52,8 @@
<constraint firstAttribute="height" constant="5" id="hYF-M2-gmn"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Xil-Cx-e0H">
<rect key="frame" x="0.0" y="5" width="375" height="50"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qhn-Dl-SDM">
<rect key="frame" x="147.5" y="11" width="80" height="28"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Up" translatesAutoresizingMaskIntoConstraints="NO" id="x0J-mB-UXx">
<rect key="frame" x="34" y="0.0" width="12" height="6"/>
<color key="tintColor" red="0.090196078430000007" green="0.047058823530000002" blue="0.28627450980000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="12" id="bcZ-GQ-xjA"/>
<constraint firstAttribute="height" constant="6" id="uNo-fj-xZ1"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Load More" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yOu-NJ-JuL">
<rect key="frame" x="8" y="11" width="64" height="14.5"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" red="0.090196078430000007" green="0.047058823530000002" blue="0.28627450980000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.72941176470000002" green="0.6705882353" blue="0.83921568629999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="yOu-NJ-JuL" firstAttribute="leading" secondItem="qhn-Dl-SDM" secondAttribute="leading" constant="8" id="Bbr-F7-8hh"/>
<constraint firstItem="x0J-mB-UXx" firstAttribute="centerX" secondItem="qhn-Dl-SDM" secondAttribute="centerX" id="EbK-JL-tOK"/>
<constraint firstItem="x0J-mB-UXx" firstAttribute="top" secondItem="qhn-Dl-SDM" secondAttribute="top" id="IcL-92-hzI"/>
<constraint firstAttribute="trailing" secondItem="yOu-NJ-JuL" secondAttribute="trailing" constant="8" id="Rg4-dw-E4U"/>
<constraint firstItem="yOu-NJ-JuL" firstAttribute="top" secondItem="x0J-mB-UXx" secondAttribute="bottom" constant="5" id="eV8-iw-geQ"/>
<constraint firstAttribute="width" constant="80" id="pOb-XC-qGX"/>
<constraint firstAttribute="height" constant="28" id="zGF-Rz-0t9"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" red="0.72941176470000002" green="0.6705882353" blue="0.83921568629999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="qhn-Dl-SDM" firstAttribute="centerY" secondItem="Xil-Cx-e0H" secondAttribute="centerY" id="9q4-xO-HV0"/>
<constraint firstItem="qhn-Dl-SDM" firstAttribute="centerX" secondItem="Xil-Cx-e0H" secondAttribute="centerX" id="HPK-kH-Njx"/>
<constraint firstAttribute="height" constant="50" id="jwa-rX-S8A"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Gnt-jG-tVj">
<rect key="frame" x="0.0" y="55" width="375" height="100"/>
<rect key="frame" x="0.0" y="5" width="375" height="100"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="ecc-6t-6K0">
<rect key="frame" x="8" y="18" width="40" height="40"/>
@ -138,6 +96,7 @@
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<gestureRecognizers/>
<constraints>
<constraint firstItem="sQQ-h7-F55" firstAttribute="leading" secondItem="PKb-3W-PjE" secondAttribute="leading" id="0er-aq-WW5"/>
<constraint firstItem="PKb-3W-PjE" firstAttribute="top" secondItem="kjf-lJ-8Md" secondAttribute="bottom" constant="4" id="1Vq-cG-Iwz"/>
@ -157,9 +116,12 @@
<constraint firstItem="PKb-3W-PjE" firstAttribute="leading" secondItem="ecc-6t-6K0" secondAttribute="trailing" constant="8" id="tY8-yq-mRV"/>
<constraint firstItem="ecc-6t-6K0" firstAttribute="top" secondItem="Gnt-jG-tVj" secondAttribute="top" constant="18" id="vVq-Ij-dgg"/>
</constraints>
<connections>
<outletCollection property="gestureRecognizers" destination="2h0-to-AXg" appends="YES" id="ZUZ-hR-mUu"/>
</connections>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="v8x-tf-zFb">
<rect key="frame" x="0.0" y="155" width="375" height="100"/>
<rect key="frame" x="0.0" y="105" width="375" height="100"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="wte-HK-KxR">
<rect key="frame" x="8" y="18" width="40" height="40"/>
@ -202,6 +164,7 @@
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<gestureRecognizers/>
<constraints>
<constraint firstAttribute="trailing" secondItem="nfb-4h-YH9" secondAttribute="trailing" constant="8" id="3k7-pa-i7N"/>
<constraint firstAttribute="trailing" secondItem="odK-vM-y2x" secondAttribute="trailing" constant="8" id="5Tx-XP-O1i"/>
@ -221,61 +184,80 @@
<constraint firstItem="wte-HK-KxR" firstAttribute="leading" secondItem="v8x-tf-zFb" secondAttribute="leading" constant="8" id="ySO-Mc-5ZM"/>
<constraint firstItem="nUk-PT-X4k" firstAttribute="top" secondItem="v8x-tf-zFb" secondAttribute="top" constant="18" id="z5T-Ab-FJ7"/>
</constraints>
<connections>
<outletCollection property="gestureRecognizers" destination="boj-gN-Kcz" appends="YES" id="0NB-Ml-hxN"/>
</connections>
</view>
<view contentMode="scaleToFill" verticalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="cAR-YB-u2k">
<rect key="frame" x="0.0" y="255" width="375" height="276"/>
<rect key="frame" x="0.0" y="205" width="375" height="376"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="TG6-9M-llp">
<rect key="frame" x="8" y="18" width="45" height="45"/>
<constraints>
<constraint firstAttribute="height" constant="45" id="PMS-nr-JKf"/>
<constraint firstAttribute="width" constant="45" id="YC5-3f-ub2"/>
</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="Tdt-bM-8kr">
<rect key="frame" x="61" y="18" width="306" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" red="0.090196078430000007" green="0.047058823530000002" blue="0.28627450980000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<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="OR8-xh-j0I">
<rect key="frame" x="61" y="40.5" width="306" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" red="0.090196078430000007" green="0.047058823530000002" blue="0.28627450980000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="Content" textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WPF-Oe-N2r">
<rect key="frame" x="8" y="78" width="359" height="190"/>
<rect key="frame" x="8" y="93" width="359" height="275"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.090196078430000007" green="0.047058823530000002" blue="0.28627450980000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<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">
<rect key="frame" x="8" y="8" width="45" height="45"/>
<constraints>
<constraint firstAttribute="height" constant="45" id="PGv-cY-D3o"/>
<constraint firstAttribute="width" constant="45" id="mbX-uR-bvo"/>
</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="a9Z-2P-cZT">
<rect key="frame" x="61" y="8" width="298" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" red="0.090196078430000007" green="0.047058823530000002" blue="0.28627450980000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<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="1xI-cX-lcs">
<rect key="frame" x="61" y="30.5" width="298" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" red="0.090196078430000007" green="0.047058823530000002" blue="0.28627450980000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<gestureRecognizers/>
<constraints>
<constraint firstItem="i10-Gq-NR9" firstAttribute="top" secondItem="50m-cW-QIF" secondAttribute="top" constant="8" id="4lV-Pa-Fk4"/>
<constraint firstAttribute="height" constant="70" id="DXt-M7-rwn"/>
<constraint firstItem="1xI-cX-lcs" firstAttribute="top" secondItem="a9Z-2P-cZT" secondAttribute="bottom" constant="2" id="Dc1-c0-QWp"/>
<constraint firstAttribute="trailing" secondItem="1xI-cX-lcs" secondAttribute="trailing" constant="8" id="IuX-dk-fGm"/>
<constraint firstItem="a9Z-2P-cZT" firstAttribute="top" secondItem="50m-cW-QIF" secondAttribute="top" constant="8" id="LsA-vW-3Ai"/>
<constraint firstItem="a9Z-2P-cZT" firstAttribute="leading" secondItem="i10-Gq-NR9" secondAttribute="trailing" constant="8" id="Mhb-Fj-TPN"/>
<constraint firstItem="i10-Gq-NR9" firstAttribute="leading" secondItem="50m-cW-QIF" secondAttribute="leading" constant="8" id="NYr-p5-5LY"/>
<constraint firstAttribute="trailing" secondItem="a9Z-2P-cZT" secondAttribute="trailing" constant="8" id="SDO-Wy-qn1"/>
<constraint firstItem="1xI-cX-lcs" firstAttribute="leading" secondItem="i10-Gq-NR9" secondAttribute="trailing" constant="8" id="tvj-tD-ppS"/>
</constraints>
<connections>
<outletCollection property="gestureRecognizers" destination="13W-G2-Xm4" appends="YES" id="6RI-ff-czd"/>
</connections>
</view>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="OR8-xh-j0I" firstAttribute="leading" secondItem="TG6-9M-llp" secondAttribute="trailing" constant="8" id="05f-4m-7IE"/>
<constraint firstItem="OR8-xh-j0I" firstAttribute="top" secondItem="Tdt-bM-8kr" secondAttribute="bottom" constant="2" id="4wJ-Lh-A81"/>
<constraint firstAttribute="bottom" secondItem="WPF-Oe-N2r" secondAttribute="bottom" constant="8" id="60R-bR-ZXD"/>
<constraint firstItem="Tdt-bM-8kr" firstAttribute="top" secondItem="cAR-YB-u2k" secondAttribute="top" constant="18" id="SrP-wT-IWD"/>
<constraint firstAttribute="trailing" secondItem="Tdt-bM-8kr" secondAttribute="trailing" constant="8" id="ecd-9J-ZWS"/>
<constraint firstAttribute="trailing" secondItem="OR8-xh-j0I" secondAttribute="trailing" constant="8" id="eeu-z0-QBb"/>
<constraint firstItem="TG6-9M-llp" firstAttribute="top" secondItem="cAR-YB-u2k" secondAttribute="top" constant="18" id="lfs-pM-EkS"/>
<constraint firstItem="TG6-9M-llp" firstAttribute="leading" secondItem="cAR-YB-u2k" secondAttribute="leading" constant="8" id="oYi-cM-Q7k"/>
<constraint firstItem="WPF-Oe-N2r" firstAttribute="top" secondItem="TG6-9M-llp" secondAttribute="bottom" constant="15" id="sPk-F0-I9i"/>
<constraint firstItem="Tdt-bM-8kr" firstAttribute="leading" secondItem="TG6-9M-llp" secondAttribute="trailing" constant="8" id="uls-fx-wzk"/>
<constraint firstAttribute="trailing" secondItem="50m-cW-QIF" secondAttribute="trailing" constant="8" id="FxM-Hx-2Do"/>
<constraint firstItem="50m-cW-QIF" firstAttribute="top" secondItem="cAR-YB-u2k" secondAttribute="top" constant="8" id="dNY-jH-OpT"/>
<constraint firstItem="50m-cW-QIF" firstAttribute="leading" secondItem="cAR-YB-u2k" secondAttribute="leading" id="diD-1N-0MJ"/>
<constraint firstItem="WPF-Oe-N2r" firstAttribute="top" secondItem="50m-cW-QIF" secondAttribute="bottom" constant="15" id="sAS-TY-p0y"/>
<constraint firstAttribute="trailing" secondItem="WPF-Oe-N2r" secondAttribute="trailing" constant="8" id="vxV-3b-ytI"/>
<constraint firstItem="WPF-Oe-N2r" firstAttribute="leading" secondItem="cAR-YB-u2k" secondAttribute="leading" constant="8" id="wqO-6z-tPD"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XuS-iF-YYL">
<rect key="frame" x="0.0" y="531" width="375" height="1"/>
<rect key="frame" x="0.0" y="581" width="375" height="1"/>
<color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="nbr-i2-Chb"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="y5N-A7-27p">
<rect key="frame" x="0.0" y="532" width="375" height="60"/>
<rect key="frame" x="0.0" y="582" width="375" height="60"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pJk-g2-yyR">
<rect key="frame" x="8" y="15" width="155" height="30"/>
@ -400,45 +382,6 @@
<constraint firstItem="ZKM-hs-NVu" firstAttribute="leading" secondItem="pJk-g2-yyR" secondAttribute="trailing" constant="8" id="s4k-Oq-Qhh"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QYs-Yp-yiP">
<rect key="frame" x="0.0" y="592" width="375" height="50"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9yB-8Q-lYW">
<rect key="frame" x="147.5" y="11" width="80" height="28"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Down" translatesAutoresizingMaskIntoConstraints="NO" id="bq4-ha-fxY">
<rect key="frame" x="34" y="22" width="12" height="6"/>
<constraints>
<constraint firstAttribute="width" constant="12" id="Wmv-nD-o7Z"/>
<constraint firstAttribute="height" constant="6" id="ylM-tH-PTM"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Load More" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="r0T-09-Pxl">
<rect key="frame" x="8" y="2.5" width="64" height="14.5"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" red="0.090196078430000007" green="0.047058823530000002" blue="0.28627450980000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.72941176470000002" green="0.6705882353" blue="0.83921568629999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="r0T-09-Pxl" secondAttribute="trailing" constant="8" id="Cej-C6-8tJ"/>
<constraint firstAttribute="width" constant="80" id="JHf-oX-B7h"/>
<constraint firstItem="bq4-ha-fxY" firstAttribute="top" secondItem="r0T-09-Pxl" secondAttribute="bottom" constant="5" id="KqY-Yi-jbq"/>
<constraint firstAttribute="height" constant="28" id="Qu8-ZU-CPU"/>
<constraint firstItem="r0T-09-Pxl" firstAttribute="leading" secondItem="9yB-8Q-lYW" secondAttribute="leading" constant="8" id="RjE-aH-bYb"/>
<constraint firstAttribute="bottom" secondItem="bq4-ha-fxY" secondAttribute="bottom" id="gpS-st-KmW"/>
<constraint firstItem="bq4-ha-fxY" firstAttribute="centerX" secondItem="9yB-8Q-lYW" secondAttribute="centerX" id="oI9-gK-lxE"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" red="0.72941176470000002" green="0.6705882353" blue="0.83921568629999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="9yB-8Q-lYW" firstAttribute="centerY" secondItem="QYs-Yp-yiP" secondAttribute="centerY" id="7pg-7z-kPR"/>
<constraint firstItem="9yB-8Q-lYW" firstAttribute="centerX" secondItem="QYs-Yp-yiP" secondAttribute="centerX" id="Ssg-rv-ode"/>
<constraint firstAttribute="height" constant="50" id="kPI-a2-kok"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="m4t-ve-E78">
<rect key="frame" x="0.0" y="642" width="375" height="5"/>
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.93725490199999995" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -460,14 +403,28 @@
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<point key="canvasLocation" x="53.600000000000001" y="48.125937031484263"/>
</view>
<tapGestureRecognizer id="2h0-to-AXg">
<connections>
<action selector="boostViewTapped:" destination="-1" id="IMQ-jP-CFX"/>
</connections>
</tapGestureRecognizer>
<tapGestureRecognizer id="boj-gN-Kcz">
<connections>
<action selector="replyViewTapped:" destination="-1" id="bDT-2B-pYx"/>
</connections>
</tapGestureRecognizer>
<tapGestureRecognizer id="13W-G2-Xm4">
<connections>
<action selector="accountViewTapped:" destination="-1" id="juK-EX-PYY"/>
</connections>
</tapGestureRecognizer>
</objects>
<resources>
<image name="Boost Regular" width="20" height="24"/>
<image name="Clock" width="22" height="22"/>
<image name="Down" width="14" height="8"/>
<image name="Message" width="20" height="20"/>
<image name="Star Regular" width="22" height="22"/>
<image name="Up" width="14" height="8"/>
</resources>
</document>

210
elpha-ios/TimelineTableViewController.swift

@ -13,7 +13,7 @@ import UIKit
class TimelineTableViewController: UITableViewController, TimelinesTableViewControllerDelegate {
var fetchedResultsController: NSFetchedResultsController<StatusMO>? = nil
let fetchLimit = 50
let fetchLimit = 20
var loading: Bool = false {
didSet {
@ -29,17 +29,14 @@ class TimelineTableViewController: UITableViewController, TimelinesTableViewCont
override func viewDidLoad() {
super.viewDidLoad()
initializeFetchedResultsController()
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 500
let moreButtonItem = UIBarButtonItem(image: UIImage(named: "More"), style: .plain, target: self, action: #selector(more))
let cleanButtonItem = UIBarButtonItem(image: UIImage(named: "Refresh CCW"), style: .plain, target: self, action: #selector(clean))
let composeButtonItem = UIBarButtonItem(image: UIImage(named: "Compose"), style: .plain, target: self, action: #selector(compose))
navigationItem.rightBarButtonItems = [composeButtonItem, cleanButtonItem, moreButtonItem]
navigationItem.leftBarButtonItems = [moreButtonItem]
navigationItem.rightBarButtonItems = [composeButtonItem, cleanButtonItem]
if let timeline = AuthenticationManager.shared.session?.timeline {
navigationItem.title = timeline.name
@ -56,19 +53,10 @@ class TimelineTableViewController: UITableViewController, TimelinesTableViewCont
}
@objc func clean() {
guard let timeline = AuthenticationManager.shared.session?.timeline else {
return
}
timeline.mutableSetValue(forKey: "statuses").removeAllObjects()
if let boundaries = timeline.boundaries?.allObjects as? [TimelineBoundaryMO] {
boundaries.forEach { CoreDataManager.shared.context.delete($0) }
if let timeline = AuthenticationManager.shared.session?.timeline {
timeline.mutableSetValue(forKey: "statuses").removeAllObjects()
CoreDataManager.shared.saveContext()
}
timeline.mutableSetValue(forKey: "boundaries").removeAllObjects()
CoreDataManager.shared.saveContext()
}
@objc func more() {
@ -122,59 +110,33 @@ class TimelineTableViewController: UITableViewController, TimelinesTableViewCont
return
}
print("Request \(request)")
print("Request: \(request)")
client.run(request) { result in
switch result {
case .success(let remoteStatuses, _):
DispatchQueue.main.async {
let statuses = remoteStatuses.compactMap { status in
return MastodonDataManager.upsertStatus(status)
}
for (index, statusResult) in statuses.enumerated() {
if index == 0 {
let boundary = TimelineBoundaryMO(context: CoreDataManager.shared.context)
boundary.status = statusResult.model
boundary.timeline = timeline
boundary.start = true
boundary.fetchedAt = Date()
} else if index == statuses.count - 1 {
let boundary = TimelineBoundaryMO(context: CoreDataManager.shared.context)
boundary.status = statusResult.model
boundary.timeline = timeline
boundary.start = false
boundary.fetchedAt = Date()
} else {
let request = NSFetchRequest<TimelineBoundaryMO>(entityName: "TimelineBoundary")
request.predicate = NSPredicate(format: "status == %@ AND timeline == %@", statusResult.model, timeline)
do {
let results = try CoreDataManager.shared.context.fetch(request)
results.forEach { CoreDataManager.shared.context.delete($0) }
} catch {
print("\(error)")
}
case .success(let remoteStatuses, _):
DispatchQueue.main.async {
let statuses = remoteStatuses.compactMap { status in
return MastodonDataManager.upsertStatus(status)
}
statusResult.model.addToTimelines(timeline)
timeline.addToStatuses(NSSet(array: statuses.map { $0.model }))
CoreDataManager.shared.saveContext()
self.loading = false
completion(statuses, nil)
}
CoreDataManager.shared.saveContext()
self.loading = false
completion(statuses, nil)
}
case .failure(let error):
completion([], error)
case .failure(let error):
completion([], error)
}
}
}
@objc func fetchTimelineWithDefaultRange() {
fetchTimeline(withRange: .limit(fetchLimit)) { error in
guard error == nil else {
return
if error != nil {
print("\(String(describing: error))")
}
}
}
@ -212,16 +174,16 @@ class TimelineTableViewController: UITableViewController, TimelinesTableViewCont
var request: Request<[Status]>
switch timeline.name {
case "Home":
request = Timelines.home(range: requestRange)
case "Local":
request = Timelines.public(local: true, range: requestRange)
case "Federated":
request = Timelines.public(local: false, range: requestRange)
case "Favorites":
request = Favourites.all(range: requestRange)
case let tag:
request = Timelines.tag(tag!, range: requestRange)
case "Home":
request = Timelines.home(range: requestRange)
case "Local":
request = Timelines.public(local: true, range: requestRange)
case "Federated":
request = Timelines.public(local: false, range: requestRange)
case "Favorites":
request = Favourites.all(range: requestRange)
case let tag:
request = Timelines.tag(tag!, range: requestRange)
}
fetchStatuses(request: request, forTimeline: timeline) { statuses, error in
@ -231,19 +193,9 @@ class TimelineTableViewController: UITableViewController, TimelinesTableViewCont
}
let newStatuses = statuses.filter { $0.new }
if newStatuses.count > -1 {
if newStatuses.count > 0 {
DispatchQueue.main.async {
if let navigationController = self.navigationController as? TimelinesNavigationController,
let newStatusesView = navigationController.newStatusesView {
newStatusesView.setCount(newStatuses.count)
newStatusesView.isHidden = false
self.view.layoutIfNeeded()
UIView.animate(withDuration: 2.0, animations: { () -> Void in
navigationController.bottomLayoutConstraint?.constant = -20
self.view.layoutIfNeeded()
})
}
// NewStatusesView stuff
}
}
@ -303,58 +255,72 @@ extension TimelineTableViewController {
fatalError("Unable to find reusable cell")
}
guard let status = fetchedResultsController?.object(at: indexPath), let statuses = fetchedResultsController?.fetchedObjects else {
guard let status = fetchedResultsController?.object(at: indexPath) else {
fatalError("CoreData error")
}
guard let timeline = AuthenticationManager.shared.session?.timeline else {
fatalError("No timeline")
}
cell.statusView.delegate = self
cell.statusView.update(withStatus: status)
let timelinePredicate = NSPredicate(format: "timeline = %@", timeline)
if let boundary = status.boundaries?.filtered(using: timelinePredicate).first as? TimelineBoundaryMO {
if indexPath.row != 0 {
if boundary.start {
let previousStatus = statuses[indexPath.row - 1]
if let previousBoundary = previousStatus.boundaries?.filtered(using: timelinePredicate).first as? TimelineBoundaryMO {
if !previousBoundary.start {
cell.statusView.topDividerView.isHidden = true
cell.statusView.topLoadMoreView.isHidden = false
}
}
}
} else {
cell.statusView.topDividerView.isHidden = true
}
if !loading {
let statusAge = Calendar.current.dateComponents([.minute], from: status.fetchedAt!, to: Date())
let stale = statusAge.minute! > 30
let first = indexPath.row == 0
let last = indexPath.row == fetchedResultsController?.fetchedObjects?.count ?? 0 - 1
if indexPath.row < statuses.count - 1 {
if !boundary.start {
let nextStatus = statuses[indexPath.row + 1]
if let nextBoundary = nextStatus.boundaries?.filtered(using: timelinePredicate).first as? TimelineBoundaryMO {
if nextBoundary.start {
cell.statusView.bottomDividerView.isHidden = true
cell.statusView.bottomLoadMoreView.isHidden = false
}
if (!first && stale) || last {
fetchTimeline(withRange: .max(id: status.id!, limit: fetchLimit)) { error in
if error != nil {
print("\(String(describing: error))")
}
}
}
if indexPath.row == statuses.count - 1 {
cell.statusView.bottomDividerView.isHidden = true
}
}
if indexPath.row == statuses.count - 2 && !loading {
fetchTimeline(withRange: .max(id: status.id!, limit: fetchLimit)) { error in
guard error == nil else {
return
}
}
}
return cell
}
}
extension TimelineTableViewController: StatusViewDelegate {
func accountTapped(account: AccountMO) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let controller = storyboard.instantiateViewController(withIdentifier: "AccountTableViewController") as? AccountTableViewController {
controller.account = account
self.navigationController?.pushViewController(controller, animated: true)
}
}
}
extension TimelineTableViewController {
func newStatusView() -> NewStatusesView {
let newStatusesView = NewStatusesView()
newStatusesView.translatesAutoresizingMaskIntoConstraints = false
let blurEffectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffect.Style.prominent))
blurEffectView.translatesAutoresizingMaskIntoConstraints = false
blurEffectView.contentView.addSubview(newStatusesView)
blurEffectView.layer.cornerRadius = 10
blurEffectView.layer.masksToBounds = true
NSLayoutConstraint.activate([
newStatusesView.leadingAnchor.constraint(equalTo: blurEffectView.leadingAnchor),
newStatusesView.trailingAnchor.constraint(equalTo: blurEffectView.trailingAnchor),
newStatusesView.topAnchor.constraint(equalTo: blurEffectView.topAnchor),
newStatusesView.bottomAnchor.constraint(equalTo: blurEffectView.bottomAnchor),
])
view.addSubview(blurEffectView)
let bottomLayoutConstraint = blurEffectView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 100)
NSLayoutConstraint.activate([
blurEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
blurEffectView.widthAnchor.constraint(equalToConstant: 200),
blurEffectView.heightAnchor.constraint(equalToConstant: 50),
bottomLayoutConstraint,
])
return newStatusesView
}
}

37
elpha-ios/TimelinesNavigationController.swift

@ -9,40 +9,5 @@
import UIKit
class TimelinesNavigationController: UINavigationController {
public var newStatusesView: NewStatusesView? = nil
public var bottomLayoutConstraint: NSLayoutConstraint? = nil
override func viewDidLoad() {
super.viewDidLoad()
let newStatusesView = NewStatusesView()
newStatusesView.translatesAutoresizingMaskIntoConstraints = false
let blurEffectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffect.Style.prominent))
blurEffectView.translatesAutoresizingMaskIntoConstraints = false
blurEffectView.contentView.addSubview(newStatusesView)
blurEffectView.layer.cornerRadius = 10
blurEffectView.layer.masksToBounds = true
NSLayoutConstraint.activate([
newStatusesView.leadingAnchor.constraint(equalTo: blurEffectView.leadingAnchor),
newStatusesView.trailingAnchor.constraint(equalTo: blurEffectView.trailingAnchor),
newStatusesView.topAnchor.constraint(equalTo: blurEffectView.topAnchor),
newStatusesView.bottomAnchor.constraint(equalTo: blurEffectView.bottomAnchor),
])
view.addSubview(blurEffectView)
bottomLayoutConstraint = blurEffectView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 100)
NSLayoutConstraint.activate([
blurEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
blurEffectView.widthAnchor.constraint(equalToConstant: 200),
blurEffectView.heightAnchor.constraint(equalToConstant: 50),
bottomLayoutConstraint!,
])
self.newStatusesView = newStatusesView
newStatusesView.isHidden = true
}
}
Loading…
Cancel
Save