Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate and render acknowledgements as Settings.bundle #230

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ xcuserdata

# Build products
build/
.build/
*.o
*.LinkFileList
*.hmap
Expand All @@ -30,9 +31,6 @@ build/
*.dat
*.dep

# Cocoapods
Pods

# AppCode specific files
.idea/
*.iml
Expand All @@ -43,5 +41,9 @@ fastlane/Preview.html
fastlane/screenshots
fastlane/test_output

# Config files
Secrets.h
.env

# Generated files
Sources/Tropos/Resources/Settings.bundle/Acknowledgements*
1 change: 0 additions & 1 deletion .ruby-version

This file was deleted.

2 changes: 1 addition & 1 deletion Cartfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
github "ReactiveCocoa/ReactiveObjCBridge"
github "ReactiveCocoa/ReactiveObjCBridge" "as-ras-6.1.0"
github "ReactiveCocoa/ReactiveSwift"
github "mixpanel/mixpanel-iphone"
15 changes: 7 additions & 8 deletions Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
github "AliSoftware/OHHTTPStubs" "6.1.0"
github "Quick/Nimble" "v8.0.1"
github "Quick/Quick" "v1.3.0"
github "ReactiveCocoa/ReactiveObjC" "3.1.0"
github "ReactiveCocoa/ReactiveObjCBridge" "3.1.0"
github "ReactiveCocoa/ReactiveSwift" "3.1.0"
github "antitypical/Result" "3.2.4"
github "mixpanel/mixpanel-iphone" "v3.3.7"
github "AliSoftware/OHHTTPStubs" "8.0.0"
github "Quick/Nimble" "v8.0.2"
github "Quick/Quick" "v2.1.0"
github "ReactiveCocoa/ReactiveObjC" "3.1.1"
github "ReactiveCocoa/ReactiveObjCBridge" "c1c49ea807d783313f696184b55956094c993e2d"
github "ReactiveCocoa/ReactiveSwift" "6.1.0"
github "mixpanel/mixpanel-iphone" "v3.4.7"
11 changes: 11 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// swift-tools-version:4.2

import PackageDescription

let package = Package(
name: "Tropos",
targets: [
.target(name: "generate-acknowledgements", dependencies: ["Settings"]),
.target(name: "Settings"),
]
)
9 changes: 9 additions & 0 deletions Sources/Settings/Bundle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import class Foundation.Bundle

extension Bundle {
public var settingsBundle: Bundle? {
return url(forResource: "Settings", withExtension: "bundle").flatMap {
Bundle(url: $0)
}
}
}
13 changes: 13 additions & 0 deletions Sources/Settings/PreferencePage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
public struct PreferencePage {
public var preferenceSpecifiers: [PreferenceSpecifier]

public init(preferenceSpecifiers: [PreferenceSpecifier] = []) {
self.preferenceSpecifiers = preferenceSpecifiers
}
}

extension PreferencePage: Codable {
private enum CodingKeys: String, CodingKey {
case preferenceSpecifiers = "PreferenceSpecifiers"
}
}
19 changes: 19 additions & 0 deletions Sources/Settings/PreferenceSpecifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
public struct PreferenceSpecifier {
public var file: String?
public var footerText: String?
public var title: String?
public let type: String

public init(type: String) {
self.type = type
}
}

extension PreferenceSpecifier: Codable {
private enum CodingKeys: String, CodingKey {
case file = "File"
case footerText = "FooterText"
case title = "Title"
case type = "Type"
}
}
9 changes: 9 additions & 0 deletions Sources/Tropos/Extensions/Optional.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extension Optional {
func unwrap(file: StaticString = #file, line: UInt = #line) throws -> Wrapped {
if let value = self {
return value
} else {
throw NilError(file: file, line: line)
}
}
}
20 changes: 20 additions & 0 deletions Sources/Tropos/Models/NilError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Foundation

struct NilError: Error, CustomNSError {
static let lineNumberKey = "line-number"

let file: StaticString
let line: UInt

var errorDescription: String? {
return "Unexpectedly found nil while unwrapping optional"
}

var errorUserInfo: [String: Any] {
return [
NSFilePathErrorKey: file,
NSLocalizedDescriptionKey: errorDescription!,
NilError.lineNumberKey: line,
]
}
}
19 changes: 19 additions & 0 deletions Sources/Tropos/Resources/Settings.bundle/Root.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>StringsTable</key>
<string>Root</string>
<key>PreferenceSpecifiers</key>
<array>
<dict>
<key>Type</key>
<string>PSChildPaneSpecifier</string>
<key>File</key>
<string>Acknowledgements</string>
<key>Title</key>
<string>Acknowledgements</string>
</dict>
</array>
</dict>
</plist>
Binary file not shown.
74 changes: 60 additions & 14 deletions Sources/Tropos/Resources/Storyboards/Base.lproj/Main.storyboard

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import os.log
import Settings
import UIKit

final class AcknowledgementsViewController: UITableViewController {
private var acknowledgements = PreferencePage()
private var settings = Bundle.main.settingsBundle!

override func viewDidLoad() {
super.viewDidLoad()

do {
acknowledgements = try loadAcknowledgementsList()
} catch {
guard #available(iOS 10.0, *) else { return }
os_log("Failed to load acknowledgements: %{public}@", type: .error, error.localizedDescription)
}
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return acknowledgements.preferenceSpecifiers.count
}

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return NSLocalizedString("Third-Party Code", comment: "Title for Acknowledgements library list")
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let library = acknowledgements.preferenceSpecifiers[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Library", for: indexPath)
cell.textLabel?.text = library.title
return cell
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
defer { super.prepare(for: segue, sender: sender) }

guard let destination = segue.destination as? TextViewController,
let indexPath = (sender as? UITableViewCell).flatMap(tableView.indexPath(for:))
else { return }

let library = acknowledgements.preferenceSpecifiers[indexPath.row]
destination.title = library.title

do {
let libraryAcknowledgements = try loadAcknowledgements(forLibraryNamed: library.title.unwrap())
destination.text = libraryAcknowledgements.footerText
} catch {
guard #available(iOS 10.0, *) else { return }

os_log(
"Failed to load acknowledgements for library '%{public}@': %{public}@",
type: .error,
library.title ?? "nil",
error.localizedDescription
)
}
}
}

private extension AcknowledgementsViewController {
func loadAcknowledgementsList() throws -> PreferencePage {
return try settings
.url(forResource: "Acknowledgements", withExtension: "plist")
.map { try Data(contentsOf: $0) }
.map { try PropertyListDecoder().decode(PreferencePage.self, from: $0) }
.unwrap()
}

func loadAcknowledgements(forLibraryNamed libraryName: String) throws -> PreferenceSpecifier {
let page = try settings
.url(forResource: libraryName, withExtension: "plist", subdirectory: "Acknowledgements")
.map { try Data(contentsOf: $0) }
.map { try PropertyListDecoder().decode(PreferencePage.self, from: $0) }
.unwrap()

return try page.preferenceSpecifiers.first.unwrap()
sharplet marked this conversation as resolved.
Show resolved Hide resolved
}
}
12 changes: 2 additions & 10 deletions Sources/Tropos/ViewControllers/SettingsTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import UIKit

private enum SegueIdentifier: String {
case privacyPolicy = "ShowWebViewController"
case acknowledgements = "ShowTextViewController"
case acknowledgements = "ShowAcknowledgementsViewController"

init?(identifier: String?) {
guard let identifier = identifier else { return nil }
Expand Down Expand Up @@ -76,15 +76,7 @@ class SettingsTableViewController: UITableViewController {
case .privacyPolicy?:
let webViewController = segue.destination as? WebViewController
webViewController?.url = URL(string: "http://www.troposweather.com/privacy/")!
case .acknowledgements?:
let textViewController = segue.destination as? TextViewController
let fileURL = Bundle.main.url(
forResource: "Pods-Tropos-settings-metadata",
withExtension: "plist"
)
let parser = fileURL.flatMap { AcknowledgementsParser(fileURL: $0) }
textViewController?.text = parser?.displayString()
case nil:
default:
break
}
}
Expand Down
7 changes: 3 additions & 4 deletions Sources/TroposCore/Controllers/ForecastController.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import CoreLocation
import ReactiveSwift
import Result

private var locationNotFound: Error {
return NSError(domain: TRErrorDomain, code: TRError.conditionsResponseLocationNotFound.rawValue)
Expand Down Expand Up @@ -33,8 +32,8 @@ public final class ForecastController: NSObject {
urlSession = URLSession(configuration: configuration)
}

public func fetchWeatherUpdate(for placemark: CLPlacemark) -> SignalProducer<WeatherUpdate, AnyError> {
guard let location = placemark.location else { return SignalProducer(error: AnyError(locationNotFound)) }
public func fetchWeatherUpdate(for placemark: CLPlacemark) -> SignalProducer<WeatherUpdate, Error> {
guard let location = placemark.location else { return SignalProducer(error: locationNotFound) }

let today = conditionsRequest(for: location.coordinate, date: nil)
let yesterday = conditionsRequest(for: location.coordinate, date: .yesterday)
Expand All @@ -44,7 +43,7 @@ public final class ForecastController: NSObject {
}
}

private func fetch(_ conditionsRequest: URLRequest) -> SignalProducer<[String: Any], AnyError> {
private func fetch(_ conditionsRequest: URLRequest) -> SignalProducer<[String: Any], Error> {
return urlSession.reactive.data(with: conditionsRequest).attemptMap {
let (data, response) = $0
guard 200 ..< 300 ~= (response as! HTTPURLResponse).statusCode else { throw responseFailed }
Expand Down
1 change: 0 additions & 1 deletion Sources/TroposCore/Controllers/GeocodeController.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import CoreLocation
import ReactiveObjCBridge
import ReactiveSwift
import Result

@objc(TRGeocodeController)
public final class GeocodeController: NSObject {
Expand Down
9 changes: 4 additions & 5 deletions Sources/TroposCore/Controllers/LocationController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import CoreLocation
import Foundation
import ReactiveObjCBridge
import ReactiveSwift
import Result

@objc(TRLocationController)
open class LocationController: NSObject, CLLocationManagerDelegate {
private let locationManager: CLLocationManager
private let locationUpdates = Signal<CLLocation, NoError>.pipe()
private let locationUpdateError = Signal<CLError, NoError>.pipe()
private let locationUpdates = Signal<CLLocation, Never>.pipe()
private let locationUpdateError = Signal<CLError, Never>.pipe()

private let statusChanged = Signal<CLAuthorizationStatus, NoError>.pipe()
private let statusChanged = Signal<CLAuthorizationStatus, Never>.pipe()
private let authorizationStatus: Property<CLAuthorizationStatus>

@objc public init(locationManager: CLLocationManager) {
Expand Down Expand Up @@ -56,7 +55,7 @@ open class LocationController: NSObject, CLLocationManagerDelegate {
}
}

public func requestAuthorization() -> SignalProducer<Bool, NoError> {
public func requestAuthorization() -> SignalProducer<Bool, Never> {
return SignalProducer { [authorizationStatus, locationManager] observer, lifetime in
let isAuthorized = authorizationStatus.producer.filterMap { status -> Bool? in
switch status {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import ReactiveObjCBridge
import ReactiveSwift
import Result

extension ForecastController {
@objc(fetchWeatherUpdateForPlacemark:)
Expand Down
3 changes: 1 addition & 2 deletions Sources/TroposIntents/CheckWeatherIntentHandler.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Foundation
import os.log
import ReactiveSwift
import Result
import TroposCore

@available(iOS 12.0, *)
Expand All @@ -20,7 +19,7 @@ public final class CheckWeatherIntentHandler: NSObject, CheckWeatherIntentHandli
let weatherUpdate = location.requestAuthorization()
.flatMap(.latest) { _ in location.requestLocation() }
.flatMap(.latest) { location in geocode.reverseGeocode(location) }
.mapError(AnyError.init)
.mapError { $0 as Error }
.flatMap(.latest) { placemark in forecast.fetchWeatherUpdate(for: placemark) }

weatherUpdate.startWithResult { result in
Expand Down
Loading