Skip to content

Commit

Permalink
Implement fee time conditionals (fixes #848) (#880)
Browse files Browse the repository at this point in the history
* Road surfaces: tag either sett or unhewn_cobblestone, not cobblestone

See https://forum.openstreetmap.org/viewtopic.php?id=61042&p=2

* fee times WIP

* fee times WIP
  • Loading branch information
westnordost authored Feb 20, 2018
1 parent 2f2e7de commit 7c59360
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package de.westnordost.streetcomplete.quests;

import de.westnordost.streetcomplete.data.osm.OsmElementQuestType;
import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd;
import de.westnordost.streetcomplete.quests.parking_fee.AddParkingFee;
import de.westnordost.streetcomplete.quests.parking_fee.AddParkingFeeForm;

public class AddParkingFeeTest extends AOsmElementQuestTypeTest
{

@Override protected OsmElementQuestType createQuestType()
{
return new AddParkingFee(null);
}

public void testYes()
{
bundle.putBoolean(AddParkingFeeForm.FEE, true);
verify(new StringMapEntryAdd("fee", "yes"));
}

public void testNo()
{
bundle.putBoolean(AddParkingFeeForm.FEE, false);
verify(new StringMapEntryAdd("fee", "no"));
}

public void testYesButOnlyAt()
{
bundle.putBoolean(AddParkingFeeForm.FEE, false);
bundle.putString(AddParkingFeeForm.FEE_CONDITONAL_HOURS, "xyz");
verify(
new StringMapEntryAdd("fee", "no"),
new StringMapEntryAdd("fee:conditional", "yes @ (xyz)"));
}

public void testYesButNotAt()
{
bundle.putBoolean(AddParkingFeeForm.FEE, true);
bundle.putString(AddParkingFeeForm.FEE_CONDITONAL_HOURS, "xyz");
verify(
new StringMapEntryAdd("fee", "yes"),
new StringMapEntryAdd("fee:conditional", "no @ (xyz)"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,35 @@
import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder;
import de.westnordost.streetcomplete.data.osm.download.OverpassMapDataDao;
import de.westnordost.streetcomplete.quests.AbstractQuestAnswerFragment;
import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment;

public class AddParkingFee extends SimpleOverpassQuestType
{
@Inject public AddParkingFee(OverpassMapDataDao overpassServer) { super(overpassServer); }

@Override protected String getTagFilters() { return "nodes, ways with amenity=parking and access ~ yes|customers|public and !fee"; }
@Override protected String getTagFilters()
{
return "nodes, ways with" +
" amenity = parking and !fee and !fee:conditional and" +
" access ~ yes|customers|public";
}

public AbstractQuestAnswerFragment createForm() { return new YesNoQuestAnswerFragment(); }
public AbstractQuestAnswerFragment createForm() { return new AddParkingFeeForm(); }

public void applyAnswerTo(Bundle answer, StringMapChangesBuilder changes)
{
String yesno = answer.getBoolean(YesNoQuestAnswerFragment.ANSWER) ? "yes" : "no";
changes.add("fee", yesno);
boolean hasFee = answer.getBoolean(AddParkingFeeForm.FEE);
changes.add("fee", toYesNo(hasFee));

if(answer.containsKey(AddParkingFeeForm.FEE_CONDITONAL_HOURS))
{
String hours = answer.getString(AddParkingFeeForm.FEE_CONDITONAL_HOURS);
changes.add("fee:conditional", toYesNo(!hasFee) + " @ (" + hours + ")");
}
}

@Override public String getCommitMessage() { return "Add whether there is a parking fee"; }
@Override public int getIcon() { return R.drawable.ic_quest_parking_fee; }
@Override public int getTitle(@NonNull Map<String, String> tags) { return R.string.quest_parking_fee_title; }

private static String toYesNo(boolean b) { return b ? "yes" : "no"; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package de.westnordost.streetcomplete.quests.parking_fee;

import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.inject.Inject;

import de.westnordost.streetcomplete.Injector;
import de.westnordost.streetcomplete.R;
import de.westnordost.streetcomplete.quests.AbstractQuestAnswerFragment;
import de.westnordost.streetcomplete.quests.opening_hours.AddOpeningHoursAdapter;
import de.westnordost.streetcomplete.quests.opening_hours.OpeningMonths;
import de.westnordost.streetcomplete.util.Serializer;

public class AddParkingFeeForm extends AbstractQuestAnswerFragment
{

public static final String FEE = "fee",
FEE_CONDITONAL_HOURS = "fee_conditional_hours";

private static final String OPENING_HOURS_DATA = "oh_data",
IS_FEE_ONLY_AT_HOURS = "oh_fee_only_at",
IS_DEFINING_HOURS = "oh";

private boolean isDefiningHours;
private boolean isFeeOnlyAtHours;
private AddOpeningHoursAdapter openingHoursAdapter;
private Button buttonOk, buttonYes, buttonNo;

private View hoursView;

@Inject Serializer serializer;

@Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = super.onCreateView(inflater, container, savedInstanceState);

Injector.instance.getApplicationComponent().inject(this);

View buttonPanel = setButtonsView(R.layout.quest_buttonpanel_yes_no_ok);
buttonOk = buttonPanel.findViewById(R.id.buttonOk);
buttonOk.setOnClickListener(v -> onClickOk());

buttonYes = buttonPanel.findViewById(R.id.buttonYes);
buttonYes.setOnClickListener(v -> onClickYesNo(true));

buttonNo = buttonPanel.findViewById(R.id.buttonNo);
buttonNo.setOnClickListener(v -> onClickYesNo(false));

addOtherAnswer(R.string.quest_fee_answer_hours, () -> setOpeningHoursMode(true));

hoursView = setContentView(R.layout.quest_fee_hours);

ArrayList<OpeningMonths> data = loadOpeningHoursData(savedInstanceState);
openingHoursAdapter = new AddOpeningHoursAdapter(data, getActivity(), getCountryInfo());
RecyclerView openingHoursList = hoursView.findViewById(R.id.hours_list);
openingHoursList.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
openingHoursList.setAdapter(openingHoursAdapter);
openingHoursList.setNestedScrollingEnabled(false);

Button addTimes = hoursView.findViewById(R.id.btn_add);
addTimes.setOnClickListener((v) -> openingHoursAdapter.addNewWeekdays());

isFeeOnlyAtHours = savedInstanceState == null || savedInstanceState.getBoolean(IS_FEE_ONLY_AT_HOURS, true);

List<String> speedUnits = Arrays.asList(
getString(R.string.quest_fee_only_at_hours),
getString(R.string.quest_fee_not_at_hours));
Spinner select = hoursView.findViewById(R.id.select);
select.setAdapter(new ArrayAdapter<>(getContext(), R.layout.spinner_item_centered, speedUnits));
select.setSelection(isFeeOnlyAtHours ? 0 : 1);
select.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
{
@Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
isFeeOnlyAtHours = position == 0;
}

@Override public void onNothingSelected(AdapterView<?> parent) { }
});

setOpeningHoursMode(savedInstanceState != null && savedInstanceState.getBoolean(IS_DEFINING_HOURS));

return view;
}

private void onClickOk()
{
if(!hasChanges())
{
Toast.makeText(getActivity(), R.string.no_changes, Toast.LENGTH_SHORT).show();
return;
}
Bundle bundle = new Bundle();
bundle.putBoolean(FEE, !isFeeOnlyAtHours);
bundle.putString(FEE_CONDITONAL_HOURS, openingHoursAdapter.toString());
applyImmediateAnswer(bundle);
}

private void onClickYesNo(boolean answer)
{
Bundle bundle = new Bundle();
bundle.putBoolean(FEE, answer);
applyImmediateAnswer(bundle);
}

private ArrayList<OpeningMonths> loadOpeningHoursData(Bundle savedInstanceState)
{
ArrayList<OpeningMonths> data;
if(savedInstanceState != null)
{
data = serializer.toObject(savedInstanceState.getByteArray(OPENING_HOURS_DATA),ArrayList.class);
}
else
{
data = new ArrayList<>();
data.add(new OpeningMonths());
}
return data;
}

@Override public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putByteArray(OPENING_HOURS_DATA, serializer.toBytes(openingHoursAdapter.getData()));
outState.putBoolean(IS_DEFINING_HOURS, isDefiningHours);
outState.putBoolean(IS_FEE_ONLY_AT_HOURS, isFeeOnlyAtHours);
}

private void setOpeningHoursMode(boolean isDefiningHours)
{
this.isDefiningHours = isDefiningHours;

hoursView.setVisibility(isDefiningHours ? View.VISIBLE : View.GONE);
buttonOk.setVisibility(isDefiningHours ? View.VISIBLE : View.GONE);
buttonNo.setVisibility(isDefiningHours ? View.GONE : View.VISIBLE);
buttonYes.setVisibility(isDefiningHours ? View.GONE : View.VISIBLE);
}

@Override public boolean hasChanges()
{
return isDefiningHours && !openingHoursAdapter.toString().isEmpty();
}
}
31 changes: 31 additions & 0 deletions app/src/main/res/layout/quest_buttonpanel_yes_no_ok.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">

<Button
android:id="@+id/buttonOk"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
style="?android:attr/buttonBarButtonStyle"
android:text="@android:string/ok"
/>

<Button
android:id="@+id/buttonNo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
style="?android:attr/buttonBarButtonStyle"
android:text="@string/quest_generic_hasFeature_no" />

<Button
android:id="@+id/buttonYes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
style="?android:attr/buttonBarButtonStyle"
android:text="@string/quest_generic_hasFeature_yes"
/>


</merge>
41 changes: 41 additions & 0 deletions app/src/main/res/layout/quest_fee_hours.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<Spinner
android:id="@+id/select"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"/>

<TextView
android:layout_toEndOf="@id/select"
android:layout_centerVertical="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
android:text="@string/quest_fee_hours_title"/>

</RelativeLayout>

<android.support.v7.widget.RecyclerView
android:id="@+id/hours_list"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</android.support.v7.widget.RecyclerView>

<Button
android:id="@+id/btn_add"
android:text="@string/quest_fee_add_times"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
5 changes: 5 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -490,4 +490,9 @@ Otherwise, you can download another keyboard in the app store. Popular keyboards
<string name="quest_streetName_pedestrian_title">What is the name of this pedestrian street?</string>
<string name="quest_streetSurface_square_title">"What surface does this square have?"</string>
<string name="quest_streetSurface_square_name_title">"What surface does the square \"%s\" have here?"</string>
<string name="quest_fee_answer_hours">Depends on time and day</string>
<string name="quest_fee_add_times">"Add times"</string>
<string name="quest_fee_only_at_hours">Yes, but only…</string>
<string name="quest_fee_not_at_hours">Yes, but not…</string>
<string name="quest_fee_hours_title">at the following times:</string>
</resources>

0 comments on commit 7c59360

Please sign in to comment.