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

Add then component #258

Merged
merged 46 commits into from
Jul 10, 2018
Merged

Add then component #258

merged 46 commits into from
Jul 10, 2018

Conversation

bsudekum
Copy link

@bsudekum bsudekum commented Apr 9, 2018

This parses out the then text component for displaying information about the upcoming step.

UPDATES
This parses out the sub component for displaying information about the upcoming step. Also, the new lane component type here provides additional lane information to assist visual representation.

/cc @lyzidiamond @danpaz @mapbox/navigation-ios

var thenText: String?
var thenTextComponents: [VisualInstructionComponent]?
if let thenTextComponent = json["then"] as? JSONDictionary {
thenText = thenTextComponent["text"] as? String
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This key is being renamed to sub.

@vincethecoder
Copy link
Contributor

Sample JSON structure - for future reference:

screen shot 2018-05-16 at 2 12 03 pm

# Conflicts:
#	MapboxDirections/MBVisualInstruction.swift
…nt with directions and active properties. Modified the mapping of text components for the visual instructions initializer.

If the value is `[.left", .straight]`, the driver can go straight or left from that lane. This is only set when the `component` is a lane.
*/
@objc public var directions: [Any]?
Copy link
Contributor

@vincethecoder vincethecoder May 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any was used instead of ManeuverDirection -- since this objc declared property is an array of enum.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.text = text
self.imageURL = imageURL
self.type = type
self.abbreviation = abbreviation
self.abbreviationPriority = abbreviationPriority
if let directions = directions {
self.directions = directions.map { ManeuverDirection(description: $0) ?? .none }
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think directions is an array of ManeuverDirctions. Try using the LaneIndication initializer here.

Copy link
Contributor

@vincethecoder vincethecoder May 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bsudekum yes (according to the recently published api-docs), when type == .lane we do have an array of directions.

Copy link
Contributor

@vincethecoder vincethecoder May 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bsudekum checkout the new structure for lane and new directions field.

screen shot 2018-05-18 at 5 26 37 am

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vincethecoder I'm saying that internally in this library, these are LaneIndications and not ManeuverDirections.


If multiple lanes are active, then it used to complete the upcoming maneuver. This value is set to `false` by default.
*/
@objc public var active: Bool = false
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This property name does not really convey the link between it's name and whether or not the lane usable.

/**
The step does not have a particular visual instruction component type associated with it.

This visual instruction component type is used as a workaround for bridging to Objective-C which does not support nullable enumeration-typed values.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to state this is work around, we do this elsewhere with other enums. Fine to say there is no visual component in this case.

…variable name, active. Removed unnecessary comment in visual instruction type.

If the value is `[.left", .straight]`, the driver can go straight or left from that lane. This is only set when the `component` is a lane.
*/
@objc public var directions: LaneIndication
Copy link
Author

@bsudekum bsudekum May 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be an array of LaneIndication.

edit: Oh it is, internally.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also rename this indications to follow other patterns in this lib.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bsudekum Yep, it's already an array... 😸

/**
The information about the next maneuver (the one after the upcoming maneuver). This detail gives the driver heads up when the current step is short.
*/
@objc public let subInstruction: VisualInstruction?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename this property to subinstruction (and elsewhere in this PR). Precedents for this include NSCompoundPredicate.subpredicates.

Copy link
Contributor

@vincethecoder vincethecoder May 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@1ec5 I understand your preference for subinstruction based off, NSCompoundPredicate.subpredicates.
We should go for consistency here.

In this context, it feels out of place when subinstruction variable reads differently from, primaryInstruction and secondaryInstruction.

https://github.com/mapbox/MapboxDirections.swift/pull/258/files#diff-8aa869dddbf0e52feb01bd7652980dfbL42

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“Sub” isn’t a word.

Copy link
Contributor

@vincethecoder vincethecoder May 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@1ec5 okay, if that's your notion, then we're better off with a real English word like tertiary --> tertiaryInstruction. Which makes this consistent.

PS: The initial lowercase of the entire variable is less favorable.

@@ -21,6 +21,11 @@ open class VisualInstructionBanner: NSObject, NSSecureCoding {
*/
@objc public let secondaryInstruction: VisualInstruction?

/**
The information about the next maneuver (the one after the upcoming maneuver). This detail gives the driver heads up when the current step is short.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A visual instruction that is presented simultaneously to provide information about an additional maneuver that occurs in rapid succession.

/**
An array indicating which directions you can go from a lane (left, right, or straight).

If the value is `[.left", .straight]`, the driver can go left or straight from that lane. This is only set when the `component` is a `lane`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple typos in the first sentence. How about:

If the value contains both LaneIndication.left and LaneIndication.straightAhead

/**
An array indicating which directions you can go from a lane (left, right, or straight).

If the value is `[.left", .straight]`, the driver can go left or straight from that lane. This is only set when the `component` is a `lane`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This property is set to nil unless the type property is set to VisualInstructionComponentType.lane.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@1ec5 FYI, LaneIndication is an array of lane indications. For obj-C compatibility, we shouldn't set this to an optional array.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LaneIndication is an OptionSet, not an array. It just happens that Swift uses the same syntax for option sets as it does for arrays. We should say the property is set to the empty set (or 0 in Objective-C) in this case, and we should describe the property as an option set, not an array.

Copy link
Contributor

@vincethecoder vincethecoder May 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. Noted. In which case, we will indicate that this OptionSet is by default an empty set. Hence, we shall proceed to keep this as a type isActiveLane as a type LaneIndication.


If multiple lanes are active, then they can all be used to complete the upcoming maneuver. This value is set to `false` by default.
*/
@objc public var isLaneActive: Bool = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should strive for more similarities with how lane indications are normally exposed. It’s poor API design to expose multiple mutually exclusive properties that are optionally set depending on each other. Consider making VisualInstructionComponent an umbrella class or protocol for subclasses such as LaneInstructionComponent – isActive can then be a property on that subclass.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better yet, instead of a bunch of lane instruction components, build up a new Intersection object that can have a bunch of approachLanes and usableApproachLanes.


If multiple lanes are active, then they can all be used to complete the upcoming maneuver. This value is set to `false` by default.
*/
@objc public var isLaneActive: Bool = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename this property to isActiveLane, or isActive if you end up refactoring the class. Also refine the getter and setter names in Objective-C: -activeLane should be the getter, and -setActiveLane: should be the setter.

*/
@objc public init(type: VisualInstructionComponentType, text: String?, imageURL: URL?, abbreviation: String?, abbreviationPriority: Int) {
@objc public init(type: VisualInstructionComponentType, text: String?, imageURL: URL?, abbreviation: String?, abbreviationPriority: Int, indications: LaneIndication = LaneIndication(), isLaneActive: Bool = false) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot of arguments for an initializer. Since these parameters aren’t always relevant, remove them and make the corresponding properties non-constant, so that the caller can set the properties after initializing the object.

@@ -6,6 +6,11 @@ import Foundation
@objc(MBVisualInstructionComponentType)
public enum VisualInstructionComponentType: Int, CustomStringConvertible {

/**
The step does not have a particular visual instruction component type associated with it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the developer supposed to do with a none instruction? Hide it?

@vincethecoder vincethecoder self-assigned this May 21, 2018
// MARK: Component/NSSecureCoding Protocols Variables
@objc public var text: String?

@objc public var type: VisualInstructionComponentType
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These properties could use some documentation.

Copy link
Contributor

@JThramer JThramer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good, but there are some things I thought I would point out.

let componentsDictionary = json["components"] as? [JSONDictionary]
var components = [MBComponentRepresentable]()

var maneuverType: ManeuverType = .none
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, and it's not really a huge deal or anything, but IMHO argument requirements should be treated exactly the same when you initialize through coder as if you were to initialize for MBVisualInstruction.init(text:maneuverType:maneuverDirection:components:degrees).

Instead of assuming default values, I would consider making MBVisualInstruction.init(json:) a fail-able initializer that returns nil if we don't find a value in the encoder for the required arguments of the designated initializer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JThramer this pre-existing and basically refactored from https://github.com/mapbox/MapboxDirections.swift/pull/258/files/916531f27e054235e599a9f6fb9e9a8955533655#diff-361a2ac3a3f3ee2f7a0fe4bbdd15f1deL59

The deduction from the existing logic is, even if we provided a fail-able initializer, the nil value will also always return .none -- so it's a choice between being proactive vs. reactive.

The component representable protocol that comprises what the instruction banner should display.
*/
@objc(MBComponentRepresentable)
public protocol MBComponentRepresentable: class, NSSecureCoding { }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type prefixes in swift are somewhat of an anti-pattern, unless your team has a special reason (and convention) for using them. They were a convention in Objective-C because all namespaces in that language are global, but with swift's introduction of modules, this pattern is more or less deprecated. This is because even if you call your class UIViewController, MyApp.UIViewController() is different from UIKit.UIViewController().

Naming the OBJ-C bridge MBComponentRepresentable is a good call though and should be kept.

/**
A visual instruction that is presented simultaneously to provide information about an additional maneuver that occurs in rapid succession.
*/
@objc public let tertiaryInstruction: VisualInstruction?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good (abstracted because it's either a lane, or a turn component), but it may be nice to specifically call out how this property is used in our current implementation.

@objc public var indications: LaneIndication

/**
The boolean that indicates whether the component is a lane and can be used to complete the upcoming maneuver.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think since this is a LaneIndicationComponent, we already know it's a lane. I think this is more about whether the lane can be used to complete the maneuver.

}

public func encode(with coder: NSCoder) {
coder.encode(indications, forKey: "directions")
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename this to coder.encode(indications, forKey: "indications")

let maneuverDirection = ManeuverDirection(description: json["modifier"] as! String) ?? .none
let textComponents = (json["components"] as! [JSONDictionary]).map {
VisualInstructionComponent(json: $0)
let componentsDictionary = json["components"] as? [JSONDictionary]
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline this line into the if let down below instead of pulling this out into a variable.

/**
A visual instruction that is presented simultaneously to provide information about an additional maneuver that occurs in rapid succession.

This instruction could either contain the visual layout information or the lane information about the next maneuver after the upcoming maneuver.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This instruction could either contain the visual layout information or the lane information about the upcoming maneuver.

XCTAssertEqual(visualInstructions?.first?.tertiaryInstruction?.maneuverDirection, .right)
XCTAssertEqual(laneIndicationComponent.isUsable, true)
XCTAssertEqual(laneIndicationComponent.indications, [.left, .straightAhead])
XCTAssertEqual(visualInstructions?.first?.drivingSide, .right)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So does this step have both lane information and then information? I think the implementation on the server prevents this from ever occurring.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will re-check the JSON response and relay my finding.

Copy link
Author

@bsudekum bsudekum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, a few minor comments.

let step = route!.legs.first!.steps.first!
let visualInstructions = step.instructionsDisplayedAlongStep

let tertiaryInstruction = visualInstructions?.first?.tertiaryInstruction
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vincethecoder getting close! I think the final piece is to now add a test for a tertiaryInstruction where it's properties are non-nil. IE test for a then instruction instead of a lane instruction.

Copy link
Contributor

@vincethecoder vincethecoder Jun 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

@bsudekum bsudekum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't approve my own PR, but I approve.

Copy link
Contributor

@vincethecoder vincethecoder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM /cc @bsudekum

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants