Extract compose attachments into separate VC
This commit is contained in:
parent
a3303dc8fb
commit
34dccf1f37
|
@ -24,6 +24,7 @@
|
|||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
||||
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
||||
D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6028B9A2150811100F223B9 /* MastodonCache.swift */; };
|
||||
D60309B52419D4F100A465FF /* ComposeAttachmentsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60309B42419D4F100A465FF /* ComposeAttachmentsViewController.swift */; };
|
||||
D60C07E421E8176B0057FAA8 /* ComposeMediaView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */; };
|
||||
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; };
|
||||
D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; };
|
||||
|
@ -76,7 +77,7 @@
|
|||
D620483623D38075008A63EF /* ContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483523D38075008A63EF /* ContentTextView.swift */; };
|
||||
D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483723D38190008A63EF /* StatusContentTextView.swift */; };
|
||||
D626493323BD751600612E6E /* ShowCameraCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */; };
|
||||
D626493523BD94CE00612E6E /* CompositionAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493423BD94CE00612E6E /* CompositionAttachment.swift */; };
|
||||
D626493523BD94CE00612E6E /* CompositionAttachmentData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493423BD94CE00612E6E /* CompositionAttachmentData.swift */; };
|
||||
D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */; };
|
||||
D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */; };
|
||||
D626493C23C1000300612E6E /* AlbumTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493A23C1000300612E6E /* AlbumTableViewCell.swift */; };
|
||||
|
@ -109,6 +110,11 @@
|
|||
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; };
|
||||
D63569E023908A8D003DD353 /* StatusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60A4FFB238B726A008AC647 /* StatusState.swift */; };
|
||||
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */; };
|
||||
D63F9C66241C4CC3004C03CF /* AddAttachmentTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63F9C65241C4CC3004C03CF /* AddAttachmentTableViewCell.xib */; };
|
||||
D63F9C68241C4F79004C03CF /* AddAttachmentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63F9C67241C4F79004C03CF /* AddAttachmentTableViewCell.swift */; };
|
||||
D63F9C6B241C50B9004C03CF /* ComposeAttachmentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63F9C69241C50B9004C03CF /* ComposeAttachmentTableViewCell.swift */; };
|
||||
D63F9C6C241C50B9004C03CF /* ComposeAttachmentTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63F9C6A241C50B9004C03CF /* ComposeAttachmentTableViewCell.xib */; };
|
||||
D63F9C6E241D2D85004C03CF /* CompositionAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63F9C6D241D2D85004C03CF /* CompositionAttachment.swift */; };
|
||||
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */ = {isa = PBXBuildFile; fileRef = D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */; };
|
||||
D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D641C772213CAA25004B4513 /* NotificationsTableViewController.swift */; };
|
||||
D641C77F213DC78A004B4513 /* InlineTextAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */; };
|
||||
|
@ -303,6 +309,7 @@
|
|||
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
|
||||
D6028B9A2150811100F223B9 /* MastodonCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCache.swift; sourceTree = "<group>"; };
|
||||
D60309B42419D4F100A465FF /* ComposeAttachmentsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAttachmentsViewController.swift; sourceTree = "<group>"; };
|
||||
D60A4FFB238B726A008AC647 /* StatusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusState.swift; sourceTree = "<group>"; };
|
||||
D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeMediaView.xib; sourceTree = "<group>"; };
|
||||
D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseStatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||
|
@ -357,7 +364,7 @@
|
|||
D620483523D38075008A63EF /* ContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentTextView.swift; sourceTree = "<group>"; };
|
||||
D620483723D38190008A63EF /* StatusContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentTextView.swift; sourceTree = "<group>"; };
|
||||
D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShowCameraCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||
D626493423BD94CE00612E6E /* CompositionAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachment.swift; sourceTree = "<group>"; };
|
||||
D626493423BD94CE00612E6E /* CompositionAttachmentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachmentData.swift; sourceTree = "<group>"; };
|
||||
D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllPhotosTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AllPhotosTableViewCell.xib; sourceTree = "<group>"; };
|
||||
D626493A23C1000300612E6E /* AlbumTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumTableViewCell.swift; sourceTree = "<group>"; };
|
||||
|
@ -389,6 +396,11 @@
|
|||
D6333B762138D94E00CE884A /* ComposeMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeMediaView.swift; sourceTree = "<group>"; };
|
||||
D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = "<group>"; };
|
||||
D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesNavigationController.swift; sourceTree = "<group>"; };
|
||||
D63F9C65241C4CC3004C03CF /* AddAttachmentTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AddAttachmentTableViewCell.xib; sourceTree = "<group>"; };
|
||||
D63F9C67241C4F79004C03CF /* AddAttachmentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAttachmentTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D63F9C69241C50B9004C03CF /* ComposeAttachmentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAttachmentTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D63F9C6A241C50B9004C03CF /* ComposeAttachmentTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeAttachmentTableViewCell.xib; sourceTree = "<group>"; };
|
||||
D63F9C6D241D2D85004C03CF /* CompositionAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachment.swift; sourceTree = "<group>"; };
|
||||
D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = DomainBlocks.plist; sourceTree = "<group>"; };
|
||||
D641C772213CAA25004B4513 /* NotificationsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsTableViewController.swift; sourceTree = "<group>"; };
|
||||
D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineTextAttachment.swift; sourceTree = "<group>"; };
|
||||
|
@ -779,6 +791,20 @@
|
|||
path = Shortcuts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D63F9C64241C4CAA004C03CF /* Attachments */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D63F9C6D241D2D85004C03CF /* CompositionAttachment.swift */,
|
||||
D626493423BD94CE00612E6E /* CompositionAttachmentData.swift */,
|
||||
D60309B42419D4F100A465FF /* ComposeAttachmentsViewController.swift */,
|
||||
D63F9C67241C4F79004C03CF /* AddAttachmentTableViewCell.swift */,
|
||||
D63F9C65241C4CC3004C03CF /* AddAttachmentTableViewCell.xib */,
|
||||
D63F9C69241C50B9004C03CF /* ComposeAttachmentTableViewCell.swift */,
|
||||
D63F9C6A241C50B9004C03CF /* ComposeAttachmentTableViewCell.xib */,
|
||||
);
|
||||
path = Attachments;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D641C780213DD7C4004B4513 /* Screens */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -862,10 +888,10 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D6B053A023BD2BED00A066FA /* Asset Picker */,
|
||||
D63F9C64241C4CAA004C03CF /* Attachments */,
|
||||
D627FF77217E94F200CC0648 /* Drafts */,
|
||||
D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */,
|
||||
D66362702136338600C9CBA2 /* ComposeViewController.swift */,
|
||||
D626493423BD94CE00612E6E /* CompositionAttachment.swift */,
|
||||
D6285B5221EA708700FE4B39 /* StatusFormat.swift */,
|
||||
D620483123D2A6A3008A63EF /* CompositionState.swift */,
|
||||
);
|
||||
|
@ -1480,7 +1506,9 @@
|
|||
D64BC19023C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib in Resources */,
|
||||
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */,
|
||||
0411610122B442870030A9B7 /* AttachmentViewController.xib in Resources */,
|
||||
D63F9C6C241C50B9004C03CF /* ComposeAttachmentTableViewCell.xib in Resources */,
|
||||
D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */,
|
||||
D63F9C66241C4CC3004C03CF /* AddAttachmentTableViewCell.xib in Resources */,
|
||||
D60C07E421E8176B0057FAA8 /* ComposeMediaView.xib in Resources */,
|
||||
D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */,
|
||||
D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */,
|
||||
|
@ -1589,7 +1617,7 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D626493523BD94CE00612E6E /* CompositionAttachment.swift in Sources */,
|
||||
D626493523BD94CE00612E6E /* CompositionAttachmentData.swift in Sources */,
|
||||
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */,
|
||||
D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */,
|
||||
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */,
|
||||
|
@ -1626,6 +1654,7 @@
|
|||
D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */,
|
||||
D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */,
|
||||
D6945C3823AC739F005C403C /* InstanceTimelineViewController.swift in Sources */,
|
||||
D60309B52419D4F100A465FF /* ComposeAttachmentsViewController.swift in Sources */,
|
||||
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */,
|
||||
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */,
|
||||
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */,
|
||||
|
@ -1647,6 +1676,7 @@
|
|||
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */,
|
||||
D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */,
|
||||
D6AEBB4A23216F0400E5038B /* UnfollowAccountActivity.swift in Sources */,
|
||||
D63F9C6B241C50B9004C03CF /* ComposeAttachmentTableViewCell.swift in Sources */,
|
||||
D663626421360D2300C9CBA2 /* AvatarStyle.swift in Sources */,
|
||||
D679C09F215850EF00DA27FE /* XCBActions.swift in Sources */,
|
||||
D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */,
|
||||
|
@ -1656,6 +1686,7 @@
|
|||
D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */,
|
||||
D64BC19223C271D9000D0238 /* MastodonActivity.swift in Sources */,
|
||||
D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */,
|
||||
D63F9C6E241D2D85004C03CF /* CompositionAttachment.swift in Sources */,
|
||||
D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */,
|
||||
D6EBF01523C55C0900AE061B /* UIApplication+Scenes.swift in Sources */,
|
||||
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */,
|
||||
|
@ -1684,6 +1715,7 @@
|
|||
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */,
|
||||
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */,
|
||||
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */,
|
||||
D63F9C68241C4F79004C03CF /* AddAttachmentTableViewCell.swift in Sources */,
|
||||
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */,
|
||||
D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */,
|
||||
D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */,
|
||||
|
|
|
@ -39,7 +39,7 @@ class DraftsManager: Codable {
|
|||
return drafts.sorted(by: { $0.lastModified > $1.lastModified })
|
||||
}
|
||||
|
||||
func create(accountID: String, text: String, contentWarning: String?, inReplyToID: String?, attachments: [DraftAttachment]) -> Draft {
|
||||
func create(accountID: String, text: String, contentWarning: String?, inReplyToID: String?, attachments: [CompositionAttachment]) -> Draft {
|
||||
let draft = Draft(accountID: accountID, text: text, contentWarning: contentWarning, inReplyToID: inReplyToID, attachments: attachments)
|
||||
drafts.append(draft)
|
||||
return draft
|
||||
|
@ -58,11 +58,11 @@ extension DraftsManager {
|
|||
private(set) var accountID: String
|
||||
private(set) var text: String
|
||||
private(set) var contentWarning: String?
|
||||
private(set) var attachments: [DraftAttachment]
|
||||
var attachments: [CompositionAttachment]
|
||||
private(set) var inReplyToID: String?
|
||||
private(set) var lastModified: Date
|
||||
|
||||
init(accountID: String, text: String, contentWarning: String?, inReplyToID: String?, attachments: [DraftAttachment], lastModified: Date = Date()) {
|
||||
init(accountID: String, text: String, contentWarning: String?, inReplyToID: String?, attachments: [CompositionAttachment], lastModified: Date = Date()) {
|
||||
self.id = UUID()
|
||||
self.accountID = accountID
|
||||
self.text = text
|
||||
|
@ -72,7 +72,7 @@ extension DraftsManager {
|
|||
self.lastModified = lastModified
|
||||
}
|
||||
|
||||
func update(accountID: String, text: String, contentWarning: String?, attachments: [DraftAttachment]) {
|
||||
func update(accountID: String, text: String, contentWarning: String?, attachments: [CompositionAttachment]) {
|
||||
self.accountID = accountID
|
||||
self.text = text
|
||||
self.contentWarning = contentWarning
|
||||
|
@ -84,9 +84,4 @@ extension DraftsManager {
|
|||
return lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
struct DraftAttachment: Codable {
|
||||
let attachment: CompositionAttachment
|
||||
let description: String
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,15 +10,15 @@ import UIKit
|
|||
import Photos
|
||||
|
||||
protocol AssetPickerViewControllerDelegate {
|
||||
func assetPicker(_ assetPicker: AssetPickerViewController, shouldAllowAssetOfType type: CompositionAttachment.AttachmentType) -> Bool
|
||||
func assetPicker(_ assetPicker: AssetPickerViewController, didSelectAttachments attachments: [CompositionAttachment])
|
||||
func assetPicker(_ assetPicker: AssetPickerViewController, shouldAllowAssetOfType type: CompositionAttachmentData.AttachmentType) -> Bool
|
||||
func assetPicker(_ assetPicker: AssetPickerViewController, didSelectAttachments attachments: [CompositionAttachmentData])
|
||||
}
|
||||
|
||||
class AssetPickerViewController: UINavigationController {
|
||||
|
||||
var assetPickerDelegate: AssetPickerViewControllerDelegate?
|
||||
|
||||
var currentCollectionSelectedAssets: [CompositionAttachment] {
|
||||
var currentCollectionSelectedAssets: [CompositionAttachmentData] {
|
||||
if let vc = visibleViewController as? AssetCollectionViewController {
|
||||
return vc.selectedAssets.map { .asset($0) }
|
||||
} else {
|
||||
|
@ -70,7 +70,7 @@ extension AssetPickerViewController: AssetCollectionViewControllerDelegate {
|
|||
|
||||
extension AssetPickerViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
||||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
||||
let attachment: CompositionAttachment
|
||||
let attachment: CompositionAttachmentData
|
||||
if let image = info[.originalImage] as? UIImage {
|
||||
attachment = .image(image)
|
||||
} else if let url = info[.mediaURL] as? URL {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// AddAttachmentTableViewCell.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 3/13/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class AddAttachmentTableViewCell: UITableViewCell {
|
||||
|
||||
@IBOutlet weak var iconImageView: UIImageView!
|
||||
@IBOutlet weak var label: UILabel!
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
let imageName: String
|
||||
if traitCollection.userInterfaceStyle == .dark {
|
||||
imageName = "photo.fill"
|
||||
} else {
|
||||
imageName = "photo"
|
||||
}
|
||||
iconImageView.image = UIImage(systemName: imageName)
|
||||
}
|
||||
|
||||
func setEnabled(_ enabled: Bool) {
|
||||
let color = enabled ? UIColor.systemBlue : .systemGray
|
||||
iconImageView.tintColor = color
|
||||
label.textColor = color
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16092.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16082.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="80" id="4Gv-Ok-KDT" customClass="AddAttachmentTableViewCell" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="80"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="4Gv-Ok-KDT" id="wXX-bs-G7N">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="80"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="gMT-px-c1s">
|
||||
<rect key="frame" x="8" y="0.0" width="398" height="80"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="photo" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="fgi-4Y-VXH">
|
||||
<rect key="frame" x="0.0" y="31" width="24" height="17.5"/>
|
||||
<color key="tintColor" systemColor="systemBlueColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="Add image or video" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7Du-B3-9rN">
|
||||
<rect key="frame" x="40" y="30" width="358" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" systemColor="systemBlueColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="80" id="3h8-I7-wtl"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="gMT-px-c1s" firstAttribute="leading" secondItem="wXX-bs-G7N" secondAttribute="leading" constant="8" id="1Cz-v3-Rzq"/>
|
||||
<constraint firstAttribute="bottom" secondItem="gMT-px-c1s" secondAttribute="bottom" id="DFN-Nd-Baq"/>
|
||||
<constraint firstAttribute="trailing" secondItem="gMT-px-c1s" secondAttribute="trailing" constant="8" id="Omi-6C-4u6"/>
|
||||
<constraint firstItem="gMT-px-c1s" firstAttribute="top" secondItem="wXX-bs-G7N" secondAttribute="top" id="TbI-3U-6aP"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="iconImageView" destination="fgi-4Y-VXH" id="hXw-M3-5B0"/>
|
||||
<outlet property="label" destination="7Du-B3-9rN" id="yX4-nX-DnY"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="95.652173913043484" y="95.758928571428569"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="photo" catalog="system" width="128" height="93"/>
|
||||
</resources>
|
||||
</document>
|
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// ComposeAttachmentTableViewCell.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 3/13/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
//import Combine
|
||||
import Photos
|
||||
import AVFoundation
|
||||
|
||||
protocol ComposeAttachmentTableViewCellDelegate: class {
|
||||
func removeAttachment(_ cell: ComposeAttachmentTableViewCell)
|
||||
func attachmentDescriptionChanged(_ cell: ComposeAttachmentTableViewCell)
|
||||
}
|
||||
|
||||
class ComposeAttachmentTableViewCell: UITableViewCell {
|
||||
|
||||
weak var delegate: ComposeAttachmentTableViewCellDelegate?
|
||||
|
||||
@IBOutlet weak var assetImageView: UIImageView!
|
||||
@IBOutlet weak var descriptionTextView: UITextView!
|
||||
@IBOutlet weak var descriptionPlaceholderLabel: UILabel!
|
||||
|
||||
var attachment: CompositionAttachment!
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
assetImageView.layer.masksToBounds = true
|
||||
assetImageView.layer.cornerRadius = 8
|
||||
|
||||
descriptionTextView.delegate = self
|
||||
}
|
||||
|
||||
func updateUI(for attachment: CompositionAttachment) {
|
||||
self.attachment = attachment
|
||||
|
||||
descriptionTextView.text = attachment.description
|
||||
updateDescriptionPlaceholderLabel()
|
||||
|
||||
switch attachment.data {
|
||||
case let .image(image):
|
||||
assetImageView.image = image
|
||||
case let .asset(asset):
|
||||
let size = CGSize(width: 80, height: 80)
|
||||
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in
|
||||
guard self.attachment == attachment else { return }
|
||||
self.assetImageView.image = image
|
||||
}
|
||||
case let .video(url):
|
||||
let asset = AVURLAsset(url: url)
|
||||
let imageGenerator = AVAssetImageGenerator(asset: asset)
|
||||
if let cgImage = try? imageGenerator.copyCGImage(at: .zero, actualTime: nil) {
|
||||
assetImageView.image = UIImage(cgImage: cgImage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateDescriptionPlaceholderLabel() {
|
||||
descriptionPlaceholderLabel.isHidden = !descriptionTextView.text.isEmpty
|
||||
}
|
||||
|
||||
@IBAction func removeButtonPressed(_ sender: Any) {
|
||||
delegate?.removeAttachment(self)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeAttachmentTableViewCell: UITextViewDelegate {
|
||||
func textViewDidChange(_ textView: UITextView) {
|
||||
delegate?.attachmentDescriptionChanged(self)
|
||||
attachment.description = textView.text
|
||||
updateDescriptionPlaceholderLabel()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16092.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16082.1"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="96" id="KGk-i7-Jjw" customClass="ComposeAttachmentTableViewCell" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Describe for the visually impared..." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="h6T-x4-yzl">
|
||||
<rect key="frame" x="96" y="16" width="194" height="41"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="xRe-ec-Coh">
|
||||
<rect key="frame" x="8" y="8" width="304" height="80"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="GLY-o8-47z">
|
||||
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="80" id="X6q-g9-dPN"/>
|
||||
<constraint firstAttribute="height" constant="80" id="xgQ-E3-0QI"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="cwP-Eh-5dJ">
|
||||
<rect key="frame" x="84" y="0.0" width="194" height="80"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Lvf-I9-aV3">
|
||||
<rect key="frame" x="282" y="29" width="22" height="22"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="22" id="aIh-Ym-ARv"/>
|
||||
<constraint firstAttribute="width" constant="22" id="qG5-np-4Bs"/>
|
||||
</constraints>
|
||||
<state key="normal" image="xmark.circle.fill" catalog="system"/>
|
||||
<connections>
|
||||
<action selector="removeButtonPressed:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="efv-Xx-t89"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="cwP-Eh-5dJ" firstAttribute="height" secondItem="xRe-ec-Coh" secondAttribute="height" id="JPp-3t-8ow"/>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="80" id="jWo-An-3h6"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="xRe-ec-Coh" secondAttribute="bottom" constant="8" id="DOS-Wv-G3s"/>
|
||||
<constraint firstItem="xRe-ec-Coh" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="8" id="E41-OU-J0c"/>
|
||||
<constraint firstItem="h6T-x4-yzl" firstAttribute="trailing" secondItem="cwP-Eh-5dJ" secondAttribute="trailing" constant="4" id="KN2-Ve-3B2"/>
|
||||
<constraint firstItem="h6T-x4-yzl" firstAttribute="top" secondItem="cwP-Eh-5dJ" secondAttribute="top" constant="8" id="P3B-Jo-XMs"/>
|
||||
<constraint firstItem="h6T-x4-yzl" firstAttribute="leading" secondItem="cwP-Eh-5dJ" secondAttribute="leading" constant="4" id="UjP-Gs-ZjO"/>
|
||||
<constraint firstItem="xRe-ec-Coh" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="8" id="gRN-PV-gm6"/>
|
||||
<constraint firstAttribute="trailing" secondItem="xRe-ec-Coh" secondAttribute="trailing" constant="8" id="tyE-HK-4qb"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
|
||||
<connections>
|
||||
<outlet property="assetImageView" destination="GLY-o8-47z" id="hZH-ur-m4z"/>
|
||||
<outlet property="descriptionPlaceholderLabel" destination="h6T-x4-yzl" id="jBe-R0-Sfn"/>
|
||||
<outlet property="descriptionTextView" destination="cwP-Eh-5dJ" id="pxJ-zF-GKC"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="107" y="181"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="xmark.circle.fill" catalog="system" width="128" height="121"/>
|
||||
</resources>
|
||||
</document>
|
|
@ -0,0 +1,265 @@
|
|||
//
|
||||
// ComposeAttachmentsViewController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 3/11/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
protocol ComposeAttachmentsViewControllerDelegate: class {
|
||||
func composeSelectedAttachmentsDidChange()
|
||||
func composeRequiresAttachmentDescriptionsDidChange()
|
||||
}
|
||||
|
||||
class ComposeAttachmentsViewController: UITableViewController {
|
||||
|
||||
weak var mastodonController: MastodonController!
|
||||
weak var delegate: ComposeAttachmentsViewControllerDelegate?
|
||||
|
||||
private var heightConstraint: NSLayoutConstraint!
|
||||
|
||||
var attachments: [CompositionAttachment] = [] {
|
||||
didSet {
|
||||
delegate?.composeSelectedAttachmentsDidChange()
|
||||
updateAddAttachmentsButtonEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
var requiresAttachmentDescriptions: Bool {
|
||||
if Preferences.shared.requireAttachmentDescriptions {
|
||||
return !attachments.allSatisfy { $0.description.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
init(attachments: [CompositionAttachment], mastodonController: MastodonController) {
|
||||
self.attachments = attachments
|
||||
self.mastodonController = mastodonController
|
||||
super.init(style: .plain)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.estimatedRowHeight = 96
|
||||
|
||||
tableView.register(UINib(nibName: "AddAttachmentTableViewCell", bundle: .main), forCellReuseIdentifier: "addAttachment")
|
||||
tableView.register(UINib(nibName: "ComposeAttachmentTableViewCell", bundle: .main), forCellReuseIdentifier: "composeAttachment")
|
||||
|
||||
heightConstraint = tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height)
|
||||
heightConstraint.isActive = true
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
updateHeightConstraint()
|
||||
}
|
||||
|
||||
func setAttachments(_ attachments: [CompositionAttachment]) {
|
||||
self.attachments = attachments
|
||||
tableView.reloadData()
|
||||
updateHeightConstraint()
|
||||
delegate?.composeRequiresAttachmentDescriptionsDidChange()
|
||||
}
|
||||
|
||||
private func updateHeightConstraint() {
|
||||
heightConstraint.constant = tableView.contentSize.height
|
||||
}
|
||||
|
||||
private func isAddAttachmentsButtonEnabled() -> Bool {
|
||||
switch mastodonController.instance.instanceType {
|
||||
case .pleroma:
|
||||
return true
|
||||
case .mastodon:
|
||||
return !attachments.contains(where: { $0.data.type == .video }) && attachments.count < 4
|
||||
}
|
||||
}
|
||||
|
||||
private func updateAddAttachmentsButtonEnabled() {
|
||||
let cell = tableView.cellForRow(at: IndexPath(row: 0, section: 1)) as! AddAttachmentTableViewCell
|
||||
cell.setEnabled(isAddAttachmentsButtonEnabled())
|
||||
}
|
||||
|
||||
func uploadAll(stepProgress: @escaping () -> Void, completion: @escaping (_ success: Bool, _ uploadedAttachments: [Attachment]) -> Void) {
|
||||
let group = DispatchGroup()
|
||||
|
||||
var anyFailed = false
|
||||
var uploadedAttachments: [Result<Attachment, Error>?] = []
|
||||
|
||||
for (index, compAttachment) in attachments.enumerated() {
|
||||
group.enter()
|
||||
|
||||
uploadedAttachments.append(nil)
|
||||
|
||||
compAttachment.data.getData { (data, mimeType) in
|
||||
stepProgress()
|
||||
|
||||
let formAttachment = FormAttachment(mimeType: mimeType, data: data, fileName: "file")
|
||||
let request = Client.upload(attachment: formAttachment, description: compAttachment.description)
|
||||
self.mastodonController.run(request) { (response) in
|
||||
switch response {
|
||||
case let .failure(error):
|
||||
uploadedAttachments[index] = .failure(error)
|
||||
anyFailed = true
|
||||
case let .success(attachment, _):
|
||||
uploadedAttachments[index] = .success(attachment)
|
||||
}
|
||||
|
||||
stepProgress()
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
group.notify(queue: .main) {
|
||||
if anyFailed {
|
||||
let errors: [(Int, Error)] = uploadedAttachments.enumerated().compactMap { (index, result) in
|
||||
switch result {
|
||||
case let .failure(error):
|
||||
return (index, error)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
let title: String
|
||||
var message: String
|
||||
if errors.count == 1 {
|
||||
title = NSLocalizedString("Could not upload attachment", comment: "single attachment upload failed alert title")
|
||||
message = errors[0].1.localizedDescription
|
||||
} else {
|
||||
title = NSLocalizedString("Could not upload the following attachments", comment: "multiple attachment upload failures alert title")
|
||||
message = ""
|
||||
for (index, error) in errors {
|
||||
message.append("Attachment \(index + 1): \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (_) in
|
||||
completion(false, [])
|
||||
}))
|
||||
} else {
|
||||
let uploadedAttachments: [Attachment] = uploadedAttachments.compactMap {
|
||||
switch $0 {
|
||||
case let .success(attachment):
|
||||
return attachment
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
completion(true, uploadedAttachments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Table view data source
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return 2
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
switch section {
|
||||
case 0:
|
||||
return attachments.count
|
||||
case 1:
|
||||
return 1
|
||||
default:
|
||||
fatalError("invalid section \(section)")
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
switch indexPath.section {
|
||||
case 0:
|
||||
let attachment = attachments[indexPath.row]
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "composeAttachment", for: indexPath) as! ComposeAttachmentTableViewCell
|
||||
cell.delegate = self
|
||||
cell.updateUI(for: attachment)
|
||||
return cell
|
||||
case 1:
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "addAttachment", for: indexPath) as! AddAttachmentTableViewCell
|
||||
cell.setEnabled(isAddAttachmentsButtonEnabled())
|
||||
return cell
|
||||
default:
|
||||
fatalError("invalid section \(indexPath.section)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Table view delegate
|
||||
|
||||
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||
if indexPath.section == 1, isAddAttachmentsButtonEnabled() {
|
||||
return indexPath
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
if indexPath.section == 1 {
|
||||
addAttachmentPressed()
|
||||
}
|
||||
}
|
||||
|
||||
func addAttachmentPressed() {
|
||||
let sheetContainer = AssetPickerSheetContainerViewController()
|
||||
sheetContainer.assetPicker.assetPickerDelegate = self
|
||||
present(sheetContainer, animated: true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeAttachmentsViewController: AssetPickerViewControllerDelegate {
|
||||
func assetPicker(_ assetPicker: AssetPickerViewController, shouldAllowAssetOfType type: CompositionAttachmentData.AttachmentType) -> Bool {
|
||||
switch mastodonController.instance.instanceType {
|
||||
case .pleroma:
|
||||
return true
|
||||
case .mastodon:
|
||||
if (type == .video && attachments.count > 0) ||
|
||||
attachments.contains(where: { $0.data.type == .video }) ||
|
||||
assetPicker.currentCollectionSelectedAssets.contains(where: { $0.type == .video }) {
|
||||
return false
|
||||
}
|
||||
return attachments.count + assetPicker.currentCollectionSelectedAssets.count < 4
|
||||
}
|
||||
}
|
||||
|
||||
func assetPicker(_ assetPicker: AssetPickerViewController, didSelectAttachments attachments: [CompositionAttachmentData]) {
|
||||
let attachments = attachments.map {
|
||||
CompositionAttachment(data: $0)
|
||||
}
|
||||
let indexPaths = attachments.indices.map { IndexPath(row: $0 + self.attachments.count, section: 0) }
|
||||
self.attachments.append(contentsOf: attachments)
|
||||
tableView.insertRows(at: indexPaths, with: .automatic)
|
||||
updateHeightConstraint()
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeAttachmentsViewController: ComposeAttachmentTableViewCellDelegate {
|
||||
func removeAttachment(_ cell: ComposeAttachmentTableViewCell) {
|
||||
guard let indexPath = tableView.indexPath(for: cell) else { return }
|
||||
attachments.remove(at: indexPath.row)
|
||||
tableView.performBatchUpdates({
|
||||
tableView.deleteRows(at: [indexPath], with: .automatic)
|
||||
}, completion: { (_) in
|
||||
// when removing cells, we don't trigger the container height update until after the animation has completed
|
||||
// otherwise, during the animation, the height is too short and the last row briefly disappears
|
||||
self.updateHeightConstraint()
|
||||
})
|
||||
}
|
||||
|
||||
func attachmentDescriptionChanged(_ cell: ComposeAttachmentTableViewCell) {
|
||||
delegate?.composeRequiresAttachmentDescriptionsDidChange()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// CompositionAttachment.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 3/14/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class CompositionAttachment: Codable {
|
||||
let data: CompositionAttachmentData
|
||||
var description: String
|
||||
|
||||
init(data: CompositionAttachmentData, description: String = "") {
|
||||
self.data = data
|
||||
self.description = description
|
||||
}
|
||||
}
|
||||
|
||||
extension CompositionAttachment: Equatable {
|
||||
static func ==(lhs: CompositionAttachment, rhs: CompositionAttachment) -> Bool {
|
||||
return lhs.data == rhs.data
|
||||
}
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
//
|
||||
// CompositionAttachmentData.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/1/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Photos
|
||||
import MobileCoreServices
|
||||
|
||||
enum CompositionAttachmentData {
|
||||
case asset(PHAsset)
|
||||
case image(UIImage)
|
||||
case video(URL)
|
||||
|
||||
var type: AttachmentType {
|
||||
switch self {
|
||||
case let .asset(asset):
|
||||
return asset.attachmentType!
|
||||
case .image(_):
|
||||
return .image
|
||||
case .video(_):
|
||||
return .video
|
||||
}
|
||||
}
|
||||
|
||||
var isAsset: Bool {
|
||||
switch self {
|
||||
case .asset(_):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var canSaveToDraft: Bool {
|
||||
switch self {
|
||||
case .video(_):
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func getData(completion: @escaping (Data, String) -> Void) {
|
||||
switch self {
|
||||
case let .image(image):
|
||||
completion(image.pngData()!, "image/png")
|
||||
case let .asset(asset):
|
||||
if asset.mediaType == .image {
|
||||
let options = PHImageRequestOptions()
|
||||
options.version = .current
|
||||
options.deliveryMode = .highQualityFormat
|
||||
options.resizeMode = .none
|
||||
options.isNetworkAccessAllowed = true
|
||||
PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) { (data, dataUTI, orientation, info) in
|
||||
guard var data = data, let dataUTI = dataUTI else { fatalError() }
|
||||
|
||||
let mimeType: String
|
||||
if dataUTI == "public.heic" {
|
||||
// neither Mastodon nor Pleroma handles HEIC well, so convert to JPEG
|
||||
let image = CIImage(data: data)!
|
||||
let context = CIContext()
|
||||
let colorSpace = image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!
|
||||
data = context.jpegRepresentation(of: image, colorSpace: colorSpace, options: [:])!
|
||||
mimeType = "image/jpeg"
|
||||
} else {
|
||||
mimeType = UTTypeCopyPreferredTagWithClass(dataUTI as CFString, kUTTagClassMIMEType)!.takeRetainedValue() as String
|
||||
}
|
||||
|
||||
completion(data, mimeType)
|
||||
}
|
||||
} else if asset.mediaType == .video {
|
||||
let options = PHVideoRequestOptions()
|
||||
options.deliveryMode = .automatic
|
||||
options.isNetworkAccessAllowed = true
|
||||
options.version = .current
|
||||
PHImageManager.default().requestExportSession(forVideo: asset, options: options, exportPreset: AVAssetExportPresetHighestQuality) { (exportSession, info) in
|
||||
guard let exportSession = exportSession else { fatalError("failed to create export session") }
|
||||
CompositionAttachmentData.exportVideoData(session: exportSession, completion: completion)
|
||||
}
|
||||
} else {
|
||||
fatalError("assetType must be either image or video")
|
||||
}
|
||||
case let .video(url):
|
||||
let asset = AVURLAsset(url: url)
|
||||
guard let session = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else {
|
||||
fatalError("failed to create export session")
|
||||
}
|
||||
CompositionAttachmentData.exportVideoData(session: session, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private static func exportVideoData(session: AVAssetExportSession, completion: @escaping (Data, String) -> Void) {
|
||||
session.outputFileType = .mp4
|
||||
session.outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("exported_video_\(UUID())").appendingPathExtension("mp4")
|
||||
session.exportAsynchronously {
|
||||
guard session.status == .completed else { fatalError("video export failed: \(String(describing: session.error))") }
|
||||
do {
|
||||
let data = try Data(contentsOf: session.outputURL!)
|
||||
completion(data, "video/mp4")
|
||||
} catch {
|
||||
fatalError("Unable to load video: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum AttachmentType {
|
||||
case image, video
|
||||
}
|
||||
}
|
||||
|
||||
extension PHAsset {
|
||||
var attachmentType: CompositionAttachmentData.AttachmentType? {
|
||||
switch self.mediaType {
|
||||
case .image:
|
||||
return .image
|
||||
case .video:
|
||||
return .video
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CompositionAttachmentData: Codable {
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
switch self {
|
||||
case let .asset(asset):
|
||||
try container.encode("asset", forKey: .type)
|
||||
try container.encode(asset.localIdentifier, forKey: .assetIdentifier)
|
||||
case let .image(image):
|
||||
try container.encode("image", forKey: .type)
|
||||
try container.encode(image.pngData()!, forKey: .imageData)
|
||||
case .video(_):
|
||||
throw EncodingError.invalidValue(self, EncodingError.Context(codingPath: [], debugDescription: "video CompositionAttachments cannot be encoded"))
|
||||
}
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
switch try container.decode(String.self, forKey: .type) {
|
||||
case "asset":
|
||||
let identifier = try container.decode(String.self, forKey: .assetIdentifier)
|
||||
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil).firstObject else {
|
||||
throw DecodingError.dataCorruptedError(forKey: .assetIdentifier, in: container, debugDescription: "Could not fetch asset with local identifier")
|
||||
}
|
||||
self = .asset(asset)
|
||||
case "image":
|
||||
guard let image = UIImage(data: try container.decode(Data.self, forKey: .imageData)) else {
|
||||
throw DecodingError.dataCorruptedError(forKey: .imageData, in: container, debugDescription: "Could not decode UIImage from image data")
|
||||
}
|
||||
self = .image(image)
|
||||
default:
|
||||
throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "CompositionAttachment type must be one of 'image' or 'asset'")
|
||||
}
|
||||
}
|
||||
|
||||
enum CodingKeys: CodingKey {
|
||||
case type
|
||||
case imageData
|
||||
/// The local identifier of the PHAsset for this attachment
|
||||
case assetIdentifier
|
||||
}
|
||||
}
|
||||
|
||||
extension CompositionAttachmentData: Equatable {
|
||||
static func ==(lhs: CompositionAttachmentData, rhs: CompositionAttachmentData) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.asset(a), .asset(b)):
|
||||
return a.localIdentifier == b.localIdentifier
|
||||
case let (.image(a), .image(b)):
|
||||
return a == b
|
||||
case let (.video(a), .video(b)):
|
||||
return a == b
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,11 +27,6 @@ class ComposeViewController: UIViewController {
|
|||
visibilityChanged()
|
||||
}
|
||||
}
|
||||
var selectedAttachments: [CompositionAttachment] = [] {
|
||||
didSet {
|
||||
updateAttachmentViews()
|
||||
}
|
||||
}
|
||||
|
||||
var hasChanges = false
|
||||
var currentDraft: DraftsManager.Draft?
|
||||
|
@ -67,11 +62,12 @@ class ComposeViewController: UIViewController {
|
|||
@IBOutlet weak var contentWarningContainerView: UIView!
|
||||
@IBOutlet weak var contentWarningTextField: UITextField!
|
||||
|
||||
@IBOutlet weak var attachmentsStackView: UIStackView!
|
||||
@IBOutlet weak var addAttachmentButton: UIButton!
|
||||
@IBOutlet weak var composeAttachmentsContainerView: UIView!
|
||||
|
||||
@IBOutlet weak var postProgressView: SteppedProgressView!
|
||||
|
||||
var composeAttachmentsViewController: ComposeAttachmentsViewController!
|
||||
|
||||
init(inReplyTo inReplyToID: String? = nil, mentioningAcct: String? = nil, text: String? = nil, mastodonController: MastodonController) {
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
|
@ -142,9 +138,15 @@ class ComposeViewController: UIViewController {
|
|||
// we have to set the font here, because the monospaced digit font is not available in IB
|
||||
charactersRemainingLabel.font = .monospacedDigitSystemFont(ofSize: 17, weight: .regular)
|
||||
updateCharactersRemaining()
|
||||
updateAttachmentDescriptionsRequired()
|
||||
updatePlaceholder()
|
||||
|
||||
composeAttachmentsViewController = ComposeAttachmentsViewController(attachments: currentDraft?.attachments ?? [], mastodonController: mastodonController)
|
||||
composeRequiresAttachmentDescriptionsDidChange()
|
||||
composeAttachmentsViewController.delegate = self
|
||||
composeAttachmentsViewController.tableView.isScrollEnabled = false
|
||||
composeAttachmentsViewController.tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
embedChild(composeAttachmentsViewController, in: composeAttachmentsContainerView)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(contentWarningTextFieldDidChange), name: UITextField.textDidChangeNotification, object: contentWarningTextField)
|
||||
}
|
||||
|
||||
|
@ -218,18 +220,6 @@ class ComposeViewController: UIViewController {
|
|||
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
let imageName: String
|
||||
if traitCollection.userInterfaceStyle == .dark {
|
||||
imageName = "photo.fill"
|
||||
} else {
|
||||
imageName = "photo"
|
||||
}
|
||||
addAttachmentButton.setImage(UIImage(systemName: imageName), for: .normal)
|
||||
}
|
||||
|
||||
func createFormattingButtons() -> [UIBarButtonItem] {
|
||||
guard Preferences.shared.statusContentType != .plain else {
|
||||
return []
|
||||
|
@ -271,19 +261,6 @@ class ComposeViewController: UIViewController {
|
|||
scrollView.scrollIndicatorInsets = scrollView.contentInset
|
||||
}
|
||||
|
||||
func updateAttachmentDescriptionsRequired() {
|
||||
if Preferences.shared.requireAttachmentDescriptions {
|
||||
for case let mediaView as ComposeMediaView in attachmentsStackView.arrangedSubviews {
|
||||
if mediaView.descriptionTextView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
compositionState.formUnion(.requiresAttachmentDescriptions)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
compositionState.subtract(.requiresAttachmentDescriptions)
|
||||
}
|
||||
|
||||
func updateCharactersRemaining() {
|
||||
let count = CharacterCounter.count(text: statusTextView.text)
|
||||
let cwCount = contentWarningEnabled ? (contentWarningTextField.text?.count ?? 0) : 0
|
||||
|
@ -312,31 +289,6 @@ class ComposeViewController: UIViewController {
|
|||
placeholderLabel.isHidden = !statusTextView.text.isEmpty
|
||||
}
|
||||
|
||||
func updateAddAttachmentButton() {
|
||||
switch mastodonController.instance.instanceType {
|
||||
case .pleroma:
|
||||
addAttachmentButton.isEnabled = true
|
||||
case .mastodon:
|
||||
addAttachmentButton.isEnabled = selectedAttachments.count <= 4 && !selectedAttachments.contains(where: { $0.type == .video })
|
||||
}
|
||||
}
|
||||
|
||||
func updateAttachmentViews() {
|
||||
for view in attachmentsStackView.arrangedSubviews {
|
||||
if view is ComposeMediaView {
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
for attachment in selectedAttachments {
|
||||
let mediaView = ComposeMediaView.create()
|
||||
mediaView.delegate = self
|
||||
mediaView.update(attachment: attachment)
|
||||
attachmentsStackView.insertArrangedSubview(mediaView, at: attachmentsStackView.arrangedSubviews.count - 1)
|
||||
updateAddAttachmentButton()
|
||||
}
|
||||
}
|
||||
|
||||
func contentWarningStateChanged() {
|
||||
contentWarningContainerView.isHidden = !contentWarningEnabled
|
||||
if contentWarningEnabled {
|
||||
|
@ -352,13 +304,7 @@ class ComposeViewController: UIViewController {
|
|||
}
|
||||
|
||||
func saveDraft() {
|
||||
var attachments = [DraftsManager.DraftAttachment]()
|
||||
for case let mediaView as ComposeMediaView in attachmentsStackView.arrangedSubviews
|
||||
where mediaView.attachment.canSaveToDraft {
|
||||
let attachment = mediaView.attachment!
|
||||
let description = mediaView.descriptionTextView.text ?? ""
|
||||
attachments.append(.init(attachment: attachment, description: description))
|
||||
}
|
||||
let attachments = composeAttachmentsViewController.attachments
|
||||
let statusText = statusTextView.text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let cw = contentWarningEnabled ? contentWarningTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) : nil
|
||||
let account = mastodonController.accountInfo!
|
||||
|
@ -463,16 +409,6 @@ class ComposeViewController: UIViewController {
|
|||
present(UINavigationController(rootViewController: draftsVC), animated: true)
|
||||
}
|
||||
|
||||
@IBAction func addAttachmentPressed(_ sender: Any) {
|
||||
// hide keyboard before showing asset picker, so it doesn't re-appear when asset picker is closed
|
||||
contentWarningTextField.resignFirstResponder()
|
||||
statusTextView.resignFirstResponder()
|
||||
|
||||
let sheetContainer = AssetPickerSheetContainerViewController()
|
||||
sheetContainer.assetPicker.assetPickerDelegate = self
|
||||
present(sheetContainer, animated: true)
|
||||
}
|
||||
|
||||
@objc func postButtonPressed() {
|
||||
guard let text = statusTextView.text,
|
||||
!text.isEmpty else { return }
|
||||
|
@ -492,44 +428,16 @@ class ComposeViewController: UIViewController {
|
|||
let sensitive = contentWarning != nil
|
||||
let visibility = self.visibility!
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
var attachments: [Attachment?] = []
|
||||
for compAttachment in selectedAttachments {
|
||||
let index = attachments.count
|
||||
attachments.append(nil)
|
||||
|
||||
let mediaView = attachmentsStackView.arrangedSubviews[index] as! ComposeMediaView
|
||||
let description = mediaView.descriptionTextView.text
|
||||
|
||||
group.enter()
|
||||
|
||||
compAttachment.getData { (data, mimeType) in
|
||||
self.postProgressView.step()
|
||||
|
||||
let request = Client.upload(attachment: FormAttachment(mimeType: mimeType, data: data, fileName: "file"), description: description)
|
||||
self.mastodonController.run(request) { (response) in
|
||||
guard case let .success(attachment, _) = response else { fatalError() }
|
||||
|
||||
attachments[index] = attachment
|
||||
|
||||
self.postProgressView.step()
|
||||
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
postProgressView.steps = 2 + (attachments.count * 2) // 2 steps (request data, then upload) for each attachment
|
||||
postProgressView.steps = 2 + (composeAttachmentsViewController.attachments.count * 2) // 2 steps (request data, then upload) for each attachment
|
||||
postProgressView.currentStep = 1
|
||||
|
||||
group.notify(queue: .main) {
|
||||
let attachments = attachments.compactMap { $0 }
|
||||
composeAttachmentsViewController.uploadAll(stepProgress: postProgressView.step) { (success, uploadedAttachments) in
|
||||
guard success else { return }
|
||||
|
||||
let request = Client.createStatus(text: text,
|
||||
contentType: Preferences.shared.statusContentType,
|
||||
inReplyTo: self.inReplyToID,
|
||||
media: attachments,
|
||||
media: uploadedAttachments,
|
||||
sensitive: sensitive,
|
||||
spoilerText: contentWarning,
|
||||
visibility: visibility,
|
||||
|
@ -547,6 +455,7 @@ class ComposeViewController: UIViewController {
|
|||
self.postProgressView.step()
|
||||
self.dismiss(animated: true)
|
||||
|
||||
// todo: this doesn't work
|
||||
let conversationVC = ConversationTableViewController(for: status.id, mastodonController: self.mastodonController)
|
||||
self.show(conversationVC, sender: self)
|
||||
|
||||
|
@ -586,36 +495,17 @@ extension ComposeViewController: UITextViewDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
extension ComposeViewController: AssetPickerViewControllerDelegate {
|
||||
func assetPicker(_ assetPicker: AssetPickerViewController, shouldAllowAssetOfType type: CompositionAttachment.AttachmentType) -> Bool {
|
||||
switch mastodonController.instance.instanceType {
|
||||
case .pleroma:
|
||||
return true
|
||||
case .mastodon:
|
||||
if (type == .video && selectedAttachments.count > 0) ||
|
||||
selectedAttachments.contains(where: { $0.type == .video }) ||
|
||||
assetPicker.currentCollectionSelectedAssets.contains(where: { $0.type == .video }) {
|
||||
return false
|
||||
}
|
||||
return selectedAttachments.count + assetPicker.currentCollectionSelectedAssets.count < 4
|
||||
}
|
||||
}
|
||||
func assetPicker(_ assetPicker: AssetPickerViewController, didSelectAttachments attachments: [CompositionAttachment]) {
|
||||
selectedAttachments.append(contentsOf: attachments)
|
||||
updateAttachmentDescriptionsRequired()
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeViewController: ComposeMediaViewDelegate {
|
||||
func didRemoveMedia(_ mediaView: ComposeMediaView) {
|
||||
let index = attachmentsStackView.arrangedSubviews.firstIndex(of: mediaView)!
|
||||
selectedAttachments.remove(at: index)
|
||||
updateAddAttachmentButton()
|
||||
updateAttachmentDescriptionsRequired()
|
||||
extension ComposeViewController: ComposeAttachmentsViewControllerDelegate {
|
||||
func composeSelectedAttachmentsDidChange() {
|
||||
currentDraft?.attachments = composeAttachmentsViewController.attachments
|
||||
}
|
||||
|
||||
func descriptionTextViewDidChange(_ mediaView: ComposeMediaView) {
|
||||
updateAttachmentDescriptionsRequired()
|
||||
func composeRequiresAttachmentDescriptionsDidChange() {
|
||||
if composeAttachmentsViewController.requiresAttachmentDescriptions {
|
||||
compositionState.formUnion(.requiresAttachmentDescriptions)
|
||||
} else {
|
||||
compositionState.subtract(.requiresAttachmentDescriptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -657,26 +547,16 @@ extension ComposeViewController: DraftsTableViewControllerDelegate {
|
|||
updatePlaceholder()
|
||||
updateCharactersRemaining()
|
||||
|
||||
selectedAttachments = draft.attachments.map { $0.attachment }
|
||||
updateAttachmentViews()
|
||||
|
||||
for case let mediaView as ComposeMediaView in attachmentsStackView.arrangedSubviews {
|
||||
let attachment = draft.attachments.first(where: { $0.attachment == mediaView.attachment })!
|
||||
mediaView.descriptionTextView.text = attachment.description
|
||||
|
||||
// call the delegate method manually, since setting the text property doesn't call it
|
||||
mediaView.textViewDidChange(mediaView.descriptionTextView)
|
||||
}
|
||||
|
||||
updateAttachmentDescriptionsRequired()
|
||||
composeAttachmentsViewController.setAttachments(draft.attachments)
|
||||
}
|
||||
|
||||
func draftSelectionCompleted() {
|
||||
// todo: I don't think this can actually happen any more?
|
||||
// check that all the assets from the draft have been added
|
||||
if let currentDraft = currentDraft, selectedAttachments.count < currentDraft.attachments.count {
|
||||
if let currentDraft = currentDraft, composeAttachmentsViewController.attachments.count < currentDraft.attachments.count {
|
||||
// some of the assets in the draft weren't loaded, so notify the user
|
||||
|
||||
let difference = currentDraft.attachments.count - selectedAttachments.count
|
||||
let difference = currentDraft.attachments.count - composeAttachmentsViewController.attachments.count
|
||||
// todo: localize me
|
||||
let suffix = difference == 1 ? "" : "s"
|
||||
let verb = difference == 1 ? "was" : "were"
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16092.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15509"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16082.1"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ComposeViewController" customModule="Tusker" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="addAttachmentButton" destination="eEV-Yt-Njk" id="o9g-pP-dtd"/>
|
||||
<outlet property="attachmentsStackView" destination="P0F-3w-gI1" id="Bi5-EK-N3a"/>
|
||||
<outlet property="charactersRemainingLabel" destination="PMB-Wa-Ht0" id="PN9-wr-Pzu"/>
|
||||
<outlet property="composeAttachmentsContainerView" destination="YFf-I2-7eX" id="u0n-Xe-v09"/>
|
||||
<outlet property="contentView" destination="pcX-rB-RxJ" id="o95-Qa-6N7"/>
|
||||
<outlet property="contentWarningContainerView" destination="kU2-7l-MSy" id="Gnq-Jb-kCA"/>
|
||||
<outlet property="contentWarningTextField" destination="T05-p6-vTz" id="Ivu-Ll-ByO"/>
|
||||
|
@ -31,14 +30,14 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="interactive" translatesAutoresizingMaskIntoConstraints="NO" id="6Z0-Vy-hMX">
|
||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" alwaysBounceVertical="YES" keyboardDismissMode="interactive" translatesAutoresizingMaskIntoConstraints="NO" id="6Z0-Vy-hMX">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pcX-rB-RxJ">
|
||||
<view contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pcX-rB-RxJ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="371.5"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="bOB-hF-O9w">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="371.5"/>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="bOB-hF-O9w">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="419.5"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6V0-mH-Mhu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="66"/>
|
||||
|
@ -134,56 +133,10 @@
|
|||
<constraint firstAttribute="trailing" secondItem="9pn-0T-IHb" secondAttribute="trailing" constant="4" id="x7Z-8w-xgm"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="P0F-3w-gI1">
|
||||
<rect key="frame" x="0.0" y="291.5" width="375" height="80"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="752-dD-eAO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Aqk-LY-jEj">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="1"/>
|
||||
<color key="backgroundColor" systemColor="separatorColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.28999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="1" id="0C7-KP-bIQ"/>
|
||||
</constraints>
|
||||
<view contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="YFf-I2-7eX">
|
||||
<rect key="frame" x="0.0" y="291.5" width="375" height="128"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cVn-xc-LH9">
|
||||
<rect key="frame" x="0.0" y="79" width="375" height="1"/>
|
||||
<color key="backgroundColor" systemColor="separatorColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.28999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="1" id="SZ4-5b-Hcf"/>
|
||||
<constraint firstAttribute="height" constant="1" id="VIz-vl-Um4"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eEV-Yt-Njk">
|
||||
<rect key="frame" x="8" y="0.0" width="359" height="80"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="80" id="sGZ-uD-CtS"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="18"/>
|
||||
<state key="normal" title=" Add image or video" image="photo" catalog="system">
|
||||
<color key="titleColor" systemColor="systemBlueColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="addAttachmentPressed:" destination="-1" eventType="touchUpInside" id="aUR-nx-O9u"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Aqk-LY-jEj" firstAttribute="top" secondItem="752-dD-eAO" secondAttribute="top" id="6i1-Jt-AEM"/>
|
||||
<constraint firstAttribute="bottom" secondItem="eEV-Yt-Njk" secondAttribute="bottom" id="C6D-yq-PU3"/>
|
||||
<constraint firstItem="Aqk-LY-jEj" firstAttribute="leading" secondItem="752-dD-eAO" secondAttribute="leading" id="Y5a-qr-Dby"/>
|
||||
<constraint firstAttribute="bottom" secondItem="cVn-xc-LH9" secondAttribute="bottom" id="dCy-ov-086"/>
|
||||
<constraint firstItem="eEV-Yt-Njk" firstAttribute="leading" secondItem="752-dD-eAO" secondAttribute="leading" constant="8" id="enN-pq-hxK"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Aqk-LY-jEj" secondAttribute="trailing" id="h1Q-QT-wB9"/>
|
||||
<constraint firstItem="cVn-xc-LH9" firstAttribute="leading" secondItem="752-dD-eAO" secondAttribute="leading" id="oNI-gt-O9v"/>
|
||||
<constraint firstAttribute="trailing" secondItem="eEV-Yt-Njk" secondAttribute="trailing" constant="8" id="qe1-4r-oaa"/>
|
||||
<constraint firstItem="eEV-Yt-Njk" firstAttribute="top" secondItem="752-dD-eAO" secondAttribute="top" id="rpc-rE-Q57"/>
|
||||
<constraint firstAttribute="trailing" secondItem="cVn-xc-LH9" secondAttribute="trailing" id="uSI-lv-mqY"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
|
@ -222,7 +175,4 @@
|
|||
<point key="canvasLocation" x="140" y="154"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="photo" catalog="system" width="64" height="46"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
|||
import Photos
|
||||
import MobileCoreServices
|
||||
|
||||
enum CompositionAttachment {
|
||||
enum CompositionAttachmentData {
|
||||
case asset(PHAsset)
|
||||
case image(UIImage)
|
||||
case video(URL)
|
||||
|
@ -79,7 +79,7 @@ enum CompositionAttachment {
|
|||
options.version = .current
|
||||
PHImageManager.default().requestExportSession(forVideo: asset, options: options, exportPreset: AVAssetExportPresetHighestQuality) { (exportSession, info) in
|
||||
guard let exportSession = exportSession else { fatalError("failed to create export session") }
|
||||
CompositionAttachment.exportVideoData(session: exportSession, completion: completion)
|
||||
CompositionAttachmentData.exportVideoData(session: exportSession, completion: completion)
|
||||
}
|
||||
} else {
|
||||
fatalError("assetType must be either image or video")
|
||||
|
@ -89,7 +89,7 @@ enum CompositionAttachment {
|
|||
guard let session = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else {
|
||||
fatalError("failed to create export session")
|
||||
}
|
||||
CompositionAttachment.exportVideoData(session: session, completion: completion)
|
||||
CompositionAttachmentData.exportVideoData(session: session, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,7 +113,7 @@ enum CompositionAttachment {
|
|||
}
|
||||
|
||||
extension PHAsset {
|
||||
var attachmentType: CompositionAttachment.AttachmentType? {
|
||||
var attachmentType: CompositionAttachmentData.AttachmentType? {
|
||||
switch self.mediaType {
|
||||
case .image:
|
||||
return .image
|
||||
|
@ -125,7 +125,7 @@ extension PHAsset {
|
|||
}
|
||||
}
|
||||
|
||||
extension CompositionAttachment: Codable {
|
||||
extension CompositionAttachmentData: Codable {
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
|
@ -169,13 +169,15 @@ extension CompositionAttachment: Codable {
|
|||
}
|
||||
}
|
||||
|
||||
extension CompositionAttachment: Equatable {
|
||||
static func ==(lhs: CompositionAttachment, rhs: CompositionAttachment) -> Bool {
|
||||
extension CompositionAttachmentData: Equatable {
|
||||
static func ==(lhs: CompositionAttachmentData, rhs: CompositionAttachmentData) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.asset(a), .asset(b)):
|
||||
return a.localIdentifier == b.localIdentifier
|
||||
case let (.image(a), .image(b)):
|
||||
return a == b
|
||||
case let (.video(a), .video(b)):
|
||||
return a == b
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class DraftTableViewCell: UITableViewCell {
|
|||
attachmentsStackView.addArrangedSubview(imageView)
|
||||
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
|
||||
|
||||
switch attachment.attachment {
|
||||
switch attachment.data {
|
||||
case let .asset(asset):
|
||||
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in
|
||||
imageView.image = image
|
||||
|
|
|
@ -23,7 +23,7 @@ class ComposeMediaView: UIView {
|
|||
@IBOutlet weak var descriptionTextView: UITextView!
|
||||
@IBOutlet weak var placeholderLabel: UILabel!
|
||||
|
||||
var attachment: CompositionAttachment!
|
||||
var attachment: CompositionAttachmentData!
|
||||
|
||||
static func create() -> ComposeMediaView {
|
||||
return UINib(nibName: "ComposeMediaView", bundle: nil).instantiate(withOwner: nil, options: nil).first as! ComposeMediaView
|
||||
|
@ -38,7 +38,7 @@ class ComposeMediaView: UIView {
|
|||
descriptionTextView.delegate = self
|
||||
}
|
||||
|
||||
func update(attachment: CompositionAttachment) {
|
||||
func update(attachment: CompositionAttachmentData) {
|
||||
self.attachment = attachment
|
||||
|
||||
switch attachment {
|
||||
|
|
Loading…
Reference in New Issue