Files
mattermost-mobile/ios/MattermostShare/ShareViewController.swift

429 lines
16 KiB
Swift

import UIKit
import Social
import MobileCoreServices
import UploadAttachments
extension Bundle {
var displayName: String? {
return object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
}
}
class ShareViewController: SLComposeServiceViewController {
private var dispatchGroup = DispatchGroup()
private var attachments = AttachmentArray<AttachmentItem>()
private var store = StoreManager.shared() as StoreManager
private var entities: [AnyHashable:Any]? = nil
private var sessionToken: String?
private var serverURL: String?
private var message: String?
private var publicURL: String?
private var tempContainerURL: URL? = UploadSessionManager.shared.tempContainerURL() as URL?
fileprivate var selectedChannel: Item?
fileprivate var selectedTeam: Item?
// MARK: - Lifecycle methods
override func viewDidLoad() {
super.viewDidLoad()
title = Bundle.main.displayName
placeholder = "Write a message..."
entities = store.getEntities(true) as [AnyHashable:Any]?
sessionToken = store.getToken()
serverURL = store.getServerUrl()
extractDataFromContext()
if sessionToken == nil {
showErrorMessage(title: "", message: "Authentication required: Please first login using the app.", VC: self)
}
}
override func isContentValid() -> Bool {
// Do validation of contentText and/or NSExtensionContext attachments here
if (attachments.count > 0) {
let maxFileSize = store.getMaxFileSize()
if attachments.hasAttachementLargerThan(fileSize: maxFileSize) {
let readableMaxSize = formatFileSize(bytes: Double(maxFileSize))
showErrorMessage(title: "", message: "File attachments shared in Mattermost must be less than \(readableMaxSize).", VC: self)
}
}
return serverURL != nil &&
sessionToken != nil &&
attachmentsCount() == attachments.count &&
selectedTeam != nil &&
selectedChannel != nil
}
override func didSelectCancel() {
UploadSessionManager.shared.clearTempDirectory()
super.didSelectCancel()
}
override func didSelectPost() {
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
if publicURL != nil {
self.message = "\(contentText!)\n\n\(publicURL!)"
} else {
self.message = contentText
}
UploadManager.shared.uploadFiles(baseURL: serverURL!, token: sessionToken!, channelId: selectedChannel!.id!, message: message, attachments: attachments, callback: {
// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
})
}
override func loadPreviewView() -> UIView! {
if attachments.findBy(type: kUTTypeFileURL as String) {
let genericPreview = GenericPreview()
genericPreview.contentMode = .scaleAspectFit
genericPreview.clipsToBounds = true
genericPreview.isUserInteractionEnabled = false
genericPreview.addConstraints([
NSLayoutConstraint(item: genericPreview, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1.0, constant: 70),
NSLayoutConstraint(item: genericPreview, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1.0, constant: 70)
])
if attachments.count > 1 {
genericPreview.mainLabel.text = "\(attachments.count) Items"
}
return genericPreview
}
return super.loadPreviewView();
}
override func configurationItems() -> [Any]! {
var items: [SLComposeSheetConfigurationItem] = []
// To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
let teamDecks = getTeamItems()
if let teams = SLComposeSheetConfigurationItem() {
teams.title = "Team"
teams.value = selectedTeam?.title
teams.tapHandler = {
let vc = TeamsViewController()
vc.teamDecks = teamDecks
vc.delegate = self
self.pushConfigurationViewController(vc)
}
items.append(teams)
}
let channelDecks = getChannelItems(forTeamId: selectedTeam?.id)
if let channels = SLComposeSheetConfigurationItem() {
channels.title = "Channels"
channels.value = selectedChannel?.title
channels.valuePending = channelDecks == nil
channels.tapHandler = {
let vc = ChannelsViewController()
vc.channelDecks = channelDecks!
vc.navbarTitle = self.selectedTeam?.title
vc.delegate = self
self.pushConfigurationViewController(vc)
}
items.append(channels)
}
validateContent()
return items
}
// MARK: - Extension Builder
func attachmentsCount() -> Int {
var count = 0
for item in extensionContext?.inputItems as! [NSExtensionItem] {
guard let attachments = item.attachments else {return 0}
for itemProvider in attachments {
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeMovie as String) ||
itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) ||
itemProvider.hasItemConformingToTypeIdentifier(kUTTypeFileURL as String) {
count = count + 1
}
}
}
return count
}
func buildChannelSection(channels: NSArray, currentChannelId: String, key: String, title:String) -> Section {
let section = Section()
section.title = title
for channel in channels as! [NSDictionary] {
let item = Item()
let id = channel.object(forKey: "id") as? String
item.id = id
item.title = channel.object(forKey: "display_name") as? String
if id == currentChannelId {
item.selected = true
selectedChannel = item
placeholder = "Write to \(item.title!)"
}
section.items.append(item)
}
return section
}
func extractDataFromContext() {
for item in extensionContext?.inputItems as! [NSExtensionItem] {
guard let attachments = item.attachments else {continue}
for itemProvider in attachments {
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeMovie as String) {
dispatchGroup.enter()
itemProvider.loadItem(forTypeIdentifier: kUTTypeMovie as String, options: nil, completionHandler: ({item, error in
if error == nil {
if let url = item as? URL {
let attachment = self.saveAttachment(url: url)
if (attachment != nil) {
attachment?.type = kUTTypeMovie as String
self.attachments.append(attachment!)
}
}
}
self.dispatchGroup.leave()
}))
} else if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
dispatchGroup.enter()
itemProvider.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil, completionHandler: ({item, error in
if error == nil {
if let url = item as? URL {
let attachment = self.saveAttachment(url: url)
if (attachment != nil) {
attachment?.type = kUTTypeImage as String
self.attachments.append(attachment!)
}
} else if let image = item as? UIImage {
if let data = image.pngData() {
let tempImageURL = self.tempContainerURL?
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension(".png")
if (try? data.write(to: tempImageURL!)) != nil {
let attachment = self.saveAttachment(url: tempImageURL!)
if (attachment != nil) {
attachment?.type = kUTTypeImage as String
self.attachments.append(attachment!)
}
}
}
}
}
self.dispatchGroup.leave()
}))
} else if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeFileURL as String) {
dispatchGroup.enter()
itemProvider.loadItem(forTypeIdentifier: kUTTypeFileURL as String, options: nil, completionHandler: ({item, error in
if error == nil {
if let url = item as? URL {
let attachment = self.saveAttachment(url: url)
if (attachment != nil) {
attachment?.type = kUTTypeFileURL as String
self.attachments.append(attachment!)
}
}
}
self.dispatchGroup.leave()
}))
} else if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
itemProvider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: ({item, error in
if let url = item as? URL {
self.publicURL = url.absoluteString
}
}))
}
}
}
dispatchGroup.notify(queue: DispatchQueue.main) {
self.validateContent()
}
}
func getChannelsFromServerAndReload(forTeamId: String) {
var currentChannel = store.getCurrentChannel() as NSDictionary?
if currentChannel?.object(forKey: "team_id") as! String != forTeamId {
currentChannel = store.getDefaultChannel(forTeamId) as NSDictionary?
}
// If currentChannel is nil it means we don't have the channels for this team
if (currentChannel == nil) {
let urlString = "\(serverURL!)/api/v4/users/me/teams/\(forTeamId)/channels"
let url = URL(string: urlString)
var request = URLRequest(url: url!)
let auth = "Bearer \(sessionToken!)" as String
request.setValue(auth, forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let dataResponse = data,
error == nil else {
print(error?.localizedDescription ?? "Response Error")
return
}
do{
//here dataResponse received from a network request
let jsonArray = try JSONSerialization.jsonObject(with: dataResponse, options: []) as! NSArray
let channels = jsonArray.filter {element in
let channel = element as! NSDictionary
let type = channel.object(forKey: "type") as! String
return type == "O" || type == "P"
}
let ent = self.store.getEntities(false)! as NSDictionary
let mutableEntities = ent.mutableCopy() as! NSMutableDictionary
let entitiesChannels = NSDictionary(dictionary: mutableEntities.object(forKey: "channels") as! NSMutableDictionary)
.object(forKey: "channels") as! NSMutableDictionary
for item in channels {
let channel = item as! NSDictionary
entitiesChannels.setValue(channel, forKey: channel.object(forKey: "id") as! String)
}
if let entitiesData: NSData = try? JSONSerialization.data(withJSONObject: ent, options: JSONSerialization.WritingOptions.prettyPrinted) as NSData {
let jsonString = String(data: entitiesData as Data, encoding: String.Encoding.utf8)! as String
self.store.updateEntities(jsonString)
self.store.getEntities(true)
self.reloadConfigurationItems()
self.view.setNeedsDisplay()
}
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
}
}
func getChannelItems(forTeamId: String?) -> [Section]? {
var channelDecks = [Section]()
var currentChannel = store.getCurrentChannel() as NSDictionary?
if currentChannel?.object(forKey: "team_id") as? String != forTeamId {
currentChannel = store.getDefaultChannel(forTeamId) as NSDictionary?
}
if currentChannel == nil {
return nil
}
let channelsInTeamBySections = store.getChannelsBySections(forTeamId) as NSDictionary
channelDecks.append(buildChannelSection(
channels: channelsInTeamBySections.object(forKey: "public") as! NSArray,
currentChannelId: selectedChannel?.id ?? currentChannel?.object(forKey: "id") as! String,
key: "public",
title: "Public Channels"
))
channelDecks.append(buildChannelSection(
channels: channelsInTeamBySections.object(forKey: "private") as! NSArray,
currentChannelId: selectedChannel?.id ?? currentChannel?.object(forKey: "id") as! String,
key: "private",
title: "Private Channels"
))
channelDecks.append(buildChannelSection(
channels: channelsInTeamBySections.object(forKey: "direct") as! NSArray,
currentChannelId: selectedChannel?.id ?? currentChannel?.object(forKey: "id") as! String,
key: "direct",
title: "Direct Channels"
))
return channelDecks
}
func getTeamItems() -> [Item] {
var teamDecks = [Item]()
let currentTeamId = store.getCurrentTeamId()
let teams = store.getMyTeams() as NSArray?
for case let team as NSDictionary in teams! {
let item = Item()
item.title = team.object(forKey: "display_name") as! String?
item.id = team.object(forKey: "id") as! String?
item.selected = false
if (item.id == (selectedTeam?.id ?? currentTeamId)) {
item.selected = true
selectedTeam = item
}
teamDecks.append(item)
}
return teamDecks
}
func saveAttachment(url: URL) -> AttachmentItem? {
let fileMgr = FileManager.default
let fileName = url.lastPathComponent
let tempFileURL = tempContainerURL?.appendingPathComponent(fileName)
do {
if (tempFileURL != url) {
try? FileManager.default.removeItem(at: tempFileURL!)
try fileMgr.copyItem(at: url, to: tempFileURL!)
}
let attr = try fileMgr.attributesOfItem(atPath: (tempFileURL?.path)!) as NSDictionary
let attachment = AttachmentItem()
attachment.fileName = fileName
attachment.fileURL = tempFileURL
attachment.fileSize = attr.fileSize()
return attachment
} catch {
return nil
}
}
// MARK: - Utiilities
func showErrorMessage(title: String, message: String, VC: UIViewController) {
let alert: UIAlertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.default) {
UIAlertAction in
self.cancel()
}
alert.addAction(okAction)
VC.present(alert, animated: true, completion: nil)
}
func formatFileSize(bytes: Double) -> String {
guard bytes > 0 else {
return "0 bytes"
}
// Adapted from http://stackoverflow.com/a/18650828
let suffixes = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
let k: Double = 1024
let i = floor(log(bytes) / log(k))
// Format number with thousands separator and everything below 1 GB with no decimal places.
let numberFormatter = NumberFormatter()
numberFormatter.maximumFractionDigits = i < 3 ? 0 : 1
numberFormatter.numberStyle = .decimal
let numberString = numberFormatter.string(from: NSNumber(value: bytes / pow(k, i))) ?? "Unknown"
let suffix = suffixes[Int(i)]
return "\(numberString) \(suffix)"
}
}
extension ShareViewController: TeamsViewControllerDelegate {
func selectedTeam(deck: Item) {
selectedTeam = deck
selectedChannel = nil
self.getChannelsFromServerAndReload(forTeamId: deck.id!)
reloadConfigurationItems()
popConfigurationViewController()
}
}
extension ShareViewController: ChannelsViewControllerDelegate {
func selectedChannel(deck: Item) {
selectedChannel = deck
reloadConfigurationItems()
popConfigurationViewController()
}
}