The document discusses code for implementing navigation mode in an Uber clone mobile app. Key points:
1. The enterNavigationMode method removes existing UI, animates navigation toolbar out, and plots the navigation path on the map.
2. It converts the path coordinates to an array, adds it to the map, fits the camera bounds, and adds "from" and "to" tags to the map pins.
3. A back button and confirmation UI are added, with styles like RideTitle and BlackButton defined for visual elements.
4. exitNavigationMode removes the navigation elements and resets the original UI.
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
Creating an Uber Clone - Part XXIII - Transcript.pdf
1. Creating an Uber Clone - Part XXIII
Finally after all the buildup lets draw the path on the map form
2. private void enterNavigationMode(final Container pinLayer, Container navigationToolbar,
final Container layer, List<Coord> path, String from, String to, int duration) {
pinLayer.removeAll();
navigationToolbar.setY(-navigationToolbar.getHeight());
layer.getComponentAt(1).setY(getDisplayHeight());
navigationToolbar.getParent().animateUnlayout(200, 120, () -> {
if(inNavigationMode) {
return;
}
inNavigationMode = true;
callSerially(() -> {
layer.removeAll();
Coord[] pathCoords = new Coord[path.size()];
path.toArray(pathCoords);
MapContainer.MapObject pathObject = mc.addPath(pathCoords);
BoundingBox bb = BoundingBox.create(pathCoords).
extend(new BoundingBox(pathCoords[0], 0.01, 0.01)).
extend(new BoundingBox(pathCoords[pathCoords.length - 1],0.01,0.01));
mc.fitBounds(bb);
Component fromComponent = createNavigationTag(
trimmedString(from), duration / 60);
Component toComponent = createNavigationTag(trimmedString(to), -1);
MapLayout.setHorizontalAlignment(fromComponent,
enterNavigationMode
We've had quite a bit of code and it brought us to the point where the navigation UI should function as expected. This is implemented in the enterNavigationMode
method which I mentioned before. As you might recall it's invoked when clicking an entry in the search results.
This method is invoked from a callback on navigation, we have the coordinates of the path to display on the map as the arguments to the method. As you may recall
these coordinate are returned by the directions method
3. private void enterNavigationMode(final Container pinLayer, Container navigationToolbar,
final Container layer, List<Coord> path, String from, String to, int duration) {
pinLayer.removeAll();
navigationToolbar.setY(-navigationToolbar.getHeight());
layer.getComponentAt(1).setY(getDisplayHeight());
navigationToolbar.getParent().animateUnlayout(200, 120, () -> {
if(inNavigationMode) {
return;
}
inNavigationMode = true;
callSerially(() -> {
layer.removeAll();
Coord[] pathCoords = new Coord[path.size()];
path.toArray(pathCoords);
MapContainer.MapObject pathObject = mc.addPath(pathCoords);
BoundingBox bb = BoundingBox.create(pathCoords).
extend(new BoundingBox(pathCoords[0], 0.01, 0.01)).
extend(new BoundingBox(pathCoords[pathCoords.length - 1],0.01,0.01));
mc.fitBounds(bb);
Component fromComponent = createNavigationTag(
trimmedString(from), duration / 60);
Component toComponent = createNavigationTag(trimmedString(to), -1);
MapLayout.setHorizontalAlignment(fromComponent,
enterNavigationMode
The first thing we do is remove the existing search UI from the form and animate it out
4. private void enterNavigationMode(final Container pinLayer, Container navigationToolbar,
final Container layer, List<Coord> path, String from, String to, int duration) {
pinLayer.removeAll();
navigationToolbar.setY(-navigationToolbar.getHeight());
layer.getComponentAt(1).setY(getDisplayHeight());
navigationToolbar.getParent().animateUnlayout(200, 120, () -> {
if(inNavigationMode) {
return;
}
inNavigationMode = true;
callSerially(() -> {
layer.removeAll();
Coord[] pathCoords = new Coord[path.size()];
path.toArray(pathCoords);
MapContainer.MapObject pathObject = mc.addPath(pathCoords);
BoundingBox bb = BoundingBox.create(pathCoords).
extend(new BoundingBox(pathCoords[0], 0.01, 0.01)).
extend(new BoundingBox(pathCoords[pathCoords.length - 1],0.01,0.01));
mc.fitBounds(bb);
Component fromComponent = createNavigationTag(
trimmedString(from), duration / 60);
Component toComponent = createNavigationTag(trimmedString(to), -1);
MapLayout.setHorizontalAlignment(fromComponent,
enterNavigationMode
Due to the way events are chained this method can be invoked more than once in some unique cases. This works around that behavior
5. private void enterNavigationMode(final Container pinLayer, Container navigationToolbar,
final Container layer, List<Coord> path, String from, String to, int duration) {
pinLayer.removeAll();
navigationToolbar.setY(-navigationToolbar.getHeight());
layer.getComponentAt(1).setY(getDisplayHeight());
navigationToolbar.getParent().animateUnlayout(200, 120, () -> {
if(inNavigationMode) {
return;
}
inNavigationMode = true;
callSerially(() -> {
layer.removeAll();
Coord[] pathCoords = new Coord[path.size()];
path.toArray(pathCoords);
MapContainer.MapObject pathObject = mc.addPath(pathCoords);
BoundingBox bb = BoundingBox.create(pathCoords).
extend(new BoundingBox(pathCoords[0], 0.01, 0.01)).
extend(new BoundingBox(pathCoords[pathCoords.length - 1],0.01,0.01));
mc.fitBounds(bb);
Component fromComponent = createNavigationTag(
trimmedString(from), duration / 60);
Component toComponent = createNavigationTag(trimmedString(to), -1);
MapLayout.setHorizontalAlignment(fromComponent,
enterNavigationMode
We convert the path to an array and add it to the map, this uses native path plotting for these coordinates. I could have used a painter or something similar and might still
use it later on
6. private void enterNavigationMode(final Container pinLayer, Container navigationToolbar,
final Container layer, List<Coord> path, String from, String to, int duration) {
pinLayer.removeAll();
navigationToolbar.setY(-navigationToolbar.getHeight());
layer.getComponentAt(1).setY(getDisplayHeight());
navigationToolbar.getParent().animateUnlayout(200, 120, () -> {
if(inNavigationMode) {
return;
}
inNavigationMode = true;
callSerially(() -> {
layer.removeAll();
Coord[] pathCoords = new Coord[path.size()];
path.toArray(pathCoords);
MapContainer.MapObject pathObject = mc.addPath(pathCoords);
BoundingBox bb = BoundingBox.create(pathCoords).
extend(new BoundingBox(pathCoords[0], 0.01, 0.01)).
extend(new BoundingBox(pathCoords[pathCoords.length - 1],0.01,0.01));
mc.fitBounds(bb);
Component fromComponent = createNavigationTag(
trimmedString(from), duration / 60);
Component toComponent = createNavigationTag(trimmedString(to), -1);
MapLayout.setHorizontalAlignment(fromComponent,
enterNavigationMode
I move the camera to show the entire path within the Form
7. Component fromComponent = createNavigationTag(
trimmedString(from), duration / 60);
Component toComponent = createNavigationTag(trimmedString(to), -1);
MapLayout.setHorizontalAlignment(fromComponent, MapLayout.HALIGN.RIGHT);
mapLayer.add(pathCoords[0], fromComponent);
mapLayer.add(pathCoords[pathCoords.length - 1], toComponent);
whereTo.setVisible(false);
getToolbar().setVisible(false);
Button back = new Button("", "TitleCommand");
FontImage.setMaterialIcon(back, FontImage.MATERIAL_ARROW_BACK);
layer.add(NORTH, back);
back.addActionListener(e ->
exitNavigationMode(layer, fromComponent, toComponent, pathObject));
Label ride = new Label("Ride", "RideTitle");
Label taxi = new Label("Taxi", Resources.getGlobalResources().
getImage("ride.png"), "RideTitle");
taxi.setTextPosition(BOTTOM);
Label separator = new Label("", "MarginSeparator");
separator.setShowEvenIfBlank(true);
Button blackButton = new Button("Confirm", "BlackButton");
Container cnt = BoxLayout.encloseY(ride, taxi, separator, blackButton);
cnt.setUIID("Form");
layer.add(SOUTH, cnt);
enterNavigationMode
I create the two tags and add them to the UI. Notice that the from tag has a right alignment. Also notice I used the trimmedString method to limit the string length. I'll
cover that method soon
8. Component fromComponent = createNavigationTag(
trimmedString(from), duration / 60);
Component toComponent = createNavigationTag(trimmedString(to), -1);
MapLayout.setHorizontalAlignment(fromComponent, MapLayout.HALIGN.RIGHT);
mapLayer.add(pathCoords[0], fromComponent);
mapLayer.add(pathCoords[pathCoords.length - 1], toComponent);
whereTo.setVisible(false);
getToolbar().setVisible(false);
Button back = new Button("", "TitleCommand");
FontImage.setMaterialIcon(back, FontImage.MATERIAL_ARROW_BACK);
layer.add(NORTH, back);
back.addActionListener(e ->
exitNavigationMode(layer, fromComponent, toComponent, pathObject));
Label ride = new Label("Ride", "RideTitle");
Label taxi = new Label("Taxi", Resources.getGlobalResources().
getImage("ride.png"), "RideTitle");
taxi.setTextPosition(BOTTOM);
Label separator = new Label("", "MarginSeparator");
separator.setShowEvenIfBlank(true);
Button blackButton = new Button("Confirm", "BlackButton");
Container cnt = BoxLayout.encloseY(ride, taxi, separator, blackButton);
cnt.setUIID("Form");
layer.add(SOUTH, cnt);
enterNavigationMode
The back behavior is just a Button styled to look like a command it invokes the exitNavigationMode method which we will get to shortly
9. Component fromComponent = createNavigationTag(
trimmedString(from), duration / 60);
Component toComponent = createNavigationTag(trimmedString(to), -1);
MapLayout.setHorizontalAlignment(fromComponent, MapLayout.HALIGN.RIGHT);
mapLayer.add(pathCoords[0], fromComponent);
mapLayer.add(pathCoords[pathCoords.length - 1], toComponent);
whereTo.setVisible(false);
getToolbar().setVisible(false);
Button back = new Button("", "TitleCommand");
FontImage.setMaterialIcon(back, FontImage.MATERIAL_ARROW_BACK);
layer.add(NORTH, back);
back.addActionListener(e ->
exitNavigationMode(layer, fromComponent, toComponent, pathObject));
Label ride = new Label("Ride", "RideTitle");
Label taxi = new Label("Taxi", Resources.getGlobalResources().
getImage("ride.png"), "RideTitle");
taxi.setTextPosition(BOTTOM);
Label separator = new Label("", "MarginSeparator");
separator.setShowEvenIfBlank(true);
Button blackButton = new Button("Confirm", "BlackButton");
Container cnt = BoxLayout.encloseY(ride, taxi, separator, blackButton);
cnt.setUIID("Form");
layer.add(SOUTH, cnt);
enterNavigationMode
This is the UI to approve the Taxi we are ordering. I used a white Container with the Form UIID on the SOUTH portion of the form as a layer
10. private String trimmedString(String str) {
int p = str.indexOf(',');
if(p > -1) {
str = str.substring(0, p);
}
if(str.length() > 15) {
str = str.substring(0, 15);
}
return str;
}
trimmedString
Before I continue I also used the trimmedString method in the code before to trim the tag components.
There isn't much to say about this method, we rely on the fact that addresses usually have a comma after them. If the string is missing that or is too long we have special
cases for those. This guarantees a string of decent length for the tag elements.
11. private void exitNavigationMode(final Container layer,
Component fromComponent, Component toComponent,
MapContainer.MapObject pathObject) {
layer.removeAll();
fromComponent.remove();
toComponent.remove();
mc.removeMapObject(pathObject);
getToolbar().setVisible(true);
whereTo.setVisible(true);
revalidate();
inNavigationMode = false;
}
exitNavigationMode
The one last missing piece of code is the exitNavigationMode call which just removes all elements and sets the invisible pieces back to visible. It's pretty trivial…