225 lines
9.9 KiB
Swift
Raw Normal View History

import WMF
public struct LibraryUsed {
let title:String
let licenseName:String
let licenseText:String
}
class LibrariesUsedViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var libraries:[LibraryUsed] = []
@IBOutlet weak var tableView: UITableView!
@objc public static let storyboardName = "LibrariesUsed"
private static let cellReuseIdentifier = "org.wikimedia.libraries.used.cell"
private static let dataFileName = "LibrariesUsed.plist"
private static let plistLibrariesUsedKey = "LibrariesUsed"
private static let plistTitleKey = "Title"
private static let plistLicenseNameKey = "LicenseName"
private static let plistLicenseTextKey = "LicenseText"
fileprivate var theme = Theme.standard
@objc func closeButtonPushed(_ : UIBarButtonItem) {
dismiss(animated: true, completion: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(named:"close"), style: .plain, target:self, action:#selector(closeButtonPushed(_:)))
navigationItem.leftBarButtonItem?.accessibilityLabel = CommonStrings.closeButtonAccessibilityLabel
}
lazy private var tableHeaderView: UIView = {
let headerFrame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 56)
let headerView = UIView.init(frame: headerFrame)
let labelFrame = headerView.frame.insetBy(dx: 10, dy: 10)
let label = UILabel.init(frame: labelFrame)
label.adjustsFontForContentSizeCategory = true
label.font = UIFont.preferredFont(forTextStyle: .footnote)
label.textColor = self.theme.colors.primaryText
label.textAlignment = .center
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.text = String.localizedStringWithFormat(WMFLocalizedString("about-libraries-licenses-title", value:"We love open source software %1$@", comment:"Title for list of library licenses. %1$@ will be replaced with an emoji expressing our love for open source software"), "💖")
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
headerView.addSubview(label)
return headerView
}()
override func viewDidLoad() {
super.viewDidLoad()
self.apply(theme: self.theme)
view.backgroundColor = .gray400
tableView.register(UITableViewCell.self, forCellReuseIdentifier: LibrariesUsedViewController.cellReuseIdentifier)
tableView.estimatedRowHeight = 41
tableView.rowHeight = UITableView.automaticDimension
tableView.tableHeaderView = tableHeaderView
tableView.semanticContentAttribute = .forceLeftToRight
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target:nil, action:nil)
title = WMFLocalizedString("about-libraries", value:"Libraries used", comment:"Header text for libraries section (as in a collection of subprograms used to develop software) of the about page. Is not capitalised for aesthetic reasons, but could be capitalised in translations.")
let fileName = LibrariesUsedViewController.dataFileName
guard
let plistPath = Bundle.main.path(forResource: fileName.wmf_substring(before: "."), ofType: fileName.wmf_substring(after: "."))
else {
assertionFailure("Could find '\(fileName)' resource.")
return
}
libraries = librariesUsed(from: plistPath)
}
private func librariesUsed(from plistPath: String) -> [LibraryUsed] {
guard
let dict = NSDictionary(contentsOfFile: plistPath) as? [String: Any],
let librariesUsedDataArray = dict[LibrariesUsedViewController.plistLibrariesUsedKey] as? [[String: Any]]
else {
assertionFailure("\n\nUnexpected items found in '\(plistPath)' or its '\(LibrariesUsedViewController.plistLibrariesUsedKey)' array.\n\n")
return []
}
return librariesUsedDataArray
.compactMap {library -> LibraryUsed? in
guard
let title = library[LibrariesUsedViewController.plistTitleKey] as? String,
let licenseName = library[LibrariesUsedViewController.plistLicenseNameKey] as? String,
let licenseText = library[LibrariesUsedViewController.plistLicenseTextKey] as? String
else {
assertionFailure("\n\nOne of the following required keys not found in '\(LibrariesUsedViewController.plistLibrariesUsedKey)' array in '\(LibrariesUsedViewController.dataFileName)': '\(LibrariesUsedViewController.plistTitleKey)', '\(LibrariesUsedViewController.plistLicenseNameKey)', '\(LibrariesUsedViewController.plistLicenseTextKey)'\n\n")
return nil
}
return LibraryUsed.init(title: title.wmf_stringByCapitalizingFirstCharacter(usingWikipediaLanguageCode: "en"), licenseName: licenseName, licenseText: licenseText)
}
.sorted(by: {
$0.title < $1.title
})
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return libraries.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: LibrariesUsedViewController.cellReuseIdentifier, for: indexPath)
cell.contentView.semanticContentAttribute = .forceLeftToRight
cell.textLabel?.semanticContentAttribute = .forceLeftToRight
cell.textLabel?.textAlignment = .left
cell.backgroundColor = theme.colors.paperBackground
cell.textLabel?.textColor = theme.colors.primaryText
cell.selectionStyle = .default
cell.selectedBackgroundView = UIView()
cell.selectedBackgroundView?.backgroundColor = theme.colors.midBackground
let library:LibraryUsed = self.libraries[indexPath.row]
cell.textLabel?.text = library.title
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let libraryVC = LibraryUsedViewController.wmf_viewControllerFromStoryboardNamed(LibrariesUsedViewController.storyboardName)
libraryVC.apply(theme: self.theme)
let library = self.libraries[indexPath.row]
libraryVC.library = library
libraryVC.title = library.title
navigationController?.pushViewController(libraryVC, animated: true)
}
}
class LibraryUsedViewController: UIViewController {
@IBOutlet weak var textView: UITextView!
public var library: LibraryUsed?
fileprivate var theme = Theme.standard
override func viewDidLoad() {
super.viewDidLoad()
self.apply(theme: self.theme)
textView.adjustsFontForContentSizeCategory = true
textView.textContainerInset = UIEdgeInsets.init(top: 10, left: 10, bottom: 10, right: 10)
guard let licenseText = library?.licenseText else { return }
textView.text = normalizeWhitespaceForBetterReadability(from: licenseText)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
textView.setContentOffset(.zero, animated: false)
}
private var newlineOptionalWhitespaceNewlineRegex: NSRegularExpression? = {
do {
return try NSRegularExpression(pattern: "\\R\\s*\\R", options: [])
} catch {
assertionFailure("regex failed to compile")
}
return nil
}()
private var oneOrMoreWhitespaceCharactersRegex: NSRegularExpression? = {
do {
return try NSRegularExpression(pattern: "\\s+", options: [])
} catch {
assertionFailure("regex failed to compile")
}
return nil
}()
// Minimal cleanups on license text.
// - consecutive line breaks reduce to 2 line breaks
// - non-consecutive line breaks converted to spaces (similar to HTML)
// Imperfect but *vast* improvement in readability especially with line wrapping.
private func normalizeWhitespaceForBetterReadability(from licenseString: String) -> String {
guard
let multiNewlineRegex = newlineOptionalWhitespaceNewlineRegex,
let whitespaceRegex = oneOrMoreWhitespaceCharactersRegex
else {
assertionFailure("regex(s) failed to compile")
return licenseString
}
var string = licenseString
let placeholder = "#temporary_placeholder#"
string = multiNewlineRegex.stringByReplacingMatches(in: string, options: [], range: string.fullRange, withTemplate: placeholder)
string = string.components(separatedBy: .newlines).joined(separator: " ")
string = whitespaceRegex.stringByReplacingMatches(in: string, options: [], range: string.fullRange, withTemplate: " ")
string = string.replacingOccurrences(of: placeholder, with: "\n\n")
return string
}
}
extension LibrariesUsedViewController: Themeable {
public func apply(theme: Theme) {
self.theme = theme
guard viewIfLoaded != nil else {
return
}
tableView.backgroundColor = theme.colors.baseBackground
tableView.separatorColor = theme.colors.chromeBackground
tableView.reloadData()
}
}
extension LibraryUsedViewController: Themeable {
public func apply(theme: Theme) {
self.theme = theme
guard viewIfLoaded != nil else {
return
}
self.view.backgroundColor = theme.colors.baseBackground
self.textView.backgroundColor = theme.colors.baseBackground
self.textView.textColor = theme.colors.primaryText
}
}