/** * Copyright (c) 2017 Razeware LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, * distribute, sublicense, create a derivative work, and/or sell copies of the * Software in any work that is designed, intended, or marketed for pedagogical or * instructional purposes related to programming, coding, application development, * or information technology. Permission for such use, copying, modification, * merger, publication, distribution, sublicensing, creation of derivative works, * or sale is expressly withheld. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ import UIKit protocol DatabaseViewDelegate: class { func databaseShared(databaseURL: URL) func databaseRenamed(databaseURL: URL) func databaseDeleted(databaseURL: URL) } class DatabaseView: UIView { private var coverImageView: UIImageView! private var indicatorView: UIActivityIndicatorView! private var valueObservation: NSKeyValueObservation! private var textLabel: UILabel! private var databaseURL: URL! public var delegate: DatabaseViewDelegate! required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) commonInit(databasePath: "NA") } init(frame: CGRect, databaseURL: URL) { super.init(frame: frame) self.databaseURL = databaseURL commonInit(databasePath: databaseURL.path) if let image = ViewController.previewImages[databaseURL.path] { self.coverImageView.image = image } else { DispatchQueue.global().async { let downloadedImage = getPreviewImage(databasePath: databaseURL.path) DispatchQueue.main.async { self.coverImageView.image = downloadedImage ViewController.previewImages[databaseURL.path] = downloadedImage } } } } private func commonInit(databasePath: String) { // Setup the background backgroundColor = .black // Create the cover image view coverImageView = UIImageView() coverImageView.translatesAutoresizingMaskIntoConstraints = false valueObservation = coverImageView.observe(\.image, options: [.new]) { [unowned self] observed, change in if change.newValue is UIImage { self.indicatorView.stopAnimating() } } //Text Label textLabel = UILabel() textLabel.numberOfLines = 0 textLabel.text = URL(fileURLWithPath: databasePath).lastPathComponent + "\n" + URL(fileURLWithPath: databasePath).fileSizeString + "\n" + (try! URL(fileURLWithPath: databasePath).resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate!.getFormattedDate(format: "yyyy-MM-dd HH:mm:ss")) textLabel.textColor = .white textLabel.textAlignment = .left //Stack View let stackView = UIStackView() stackView.axis = NSLayoutConstraint.Axis.horizontal stackView.distribution = UIStackView.Distribution.fill stackView.alignment = UIStackView.Alignment.center stackView.spacing = 8.0 stackView.addArrangedSubview(coverImageView) stackView.addArrangedSubview(textLabel) stackView.translatesAutoresizingMaskIntoConstraints = false addSubview(stackView) // Create the indicator view indicatorView = UIActivityIndicatorView() indicatorView.translatesAutoresizingMaskIntoConstraints = false indicatorView.style = .whiteLarge indicatorView.startAnimating() addSubview(indicatorView) NSLayoutConstraint.activate([ stackView.leftAnchor.constraint(equalTo: self.leftAnchor), stackView.rightAnchor.constraint(equalTo: self.rightAnchor), stackView.topAnchor.constraint(equalTo: self.topAnchor), stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor), stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor), coverImageView.widthAnchor.constraint(equalToConstant: 180 - 5), indicatorView.centerXAnchor.constraint(equalTo: coverImageView.centerXAnchor), indicatorView.centerYAnchor.constraint(equalTo: coverImageView.centerYAnchor) ]) let interaction = UIContextMenuInteraction(delegate: self) self.addInteraction(interaction) } func highlightDatabase(_ didHighlightView: Bool) { if didHighlightView == true { backgroundColor = .white textLabel.textColor = .black } else { backgroundColor = .black textLabel.textColor = .white } } } extension URL { var attributes: [FileAttributeKey : Any]? { do { return try FileManager.default.attributesOfItem(atPath: path) } catch let error as NSError { print("FileAttribute error: \(error)") } return nil } var fileSize: UInt64 { return attributes?[.size] as? UInt64 ?? UInt64(0) } var fileSizeString: String { return ByteCountFormatter.string(fromByteCount: Int64(fileSize), countStyle: .file) } var creationDate: Date? { return attributes?[.creationDate] as? Date } } extension DatabaseView: UIContextMenuInteractionDelegate { func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in // Create an action for sharing let share = UIAction(title: "Share", image: UIImage(systemName: "square.and.arrow.up")) { action in // Show system share sheet self.delegate?.databaseShared(databaseURL: self.databaseURL) } // Create an action for renaming let rename = UIAction(title: "Rename", image: UIImage(systemName: "square.and.pencil")) { action in // Perform renaming self.delegate?.databaseRenamed(databaseURL: self.databaseURL) } // Here we specify the "destructive" attribute to show that it’s destructive in nature let delete = UIAction(title: "Delete", image: UIImage(systemName: "trash"), attributes: .destructive) { action in // Perform delete self.delegate?.databaseDeleted(databaseURL: self.databaseURL) } // Create and return a UIMenu with all of the actions as children return UIMenu(title: "", children: [share, rename, delete]) } } }