diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/directions/model/Direction.java b/onebusaway-android/src/main/java/org/onebusaway/android/directions/model/Direction.java index 1215b6a26..addc968c0 100644 --- a/onebusaway-android/src/main/java/org/onebusaway/android/directions/model/Direction.java +++ b/onebusaway-android/src/main/java/org/onebusaway/android/directions/model/Direction.java @@ -42,6 +42,8 @@ public class Direction { private CharSequence newTime = null; + private CharSequence shortTime; + private boolean isTransit = false; private ArrayList subDirections = null; @@ -176,4 +178,12 @@ public CharSequence getExtra() { public void setExtra(CharSequence extra) { this.extra = extra; } + + public CharSequence getShortTime() { + return shortTime; + } + + public void setShortTime(CharSequence shortTime) { + this.shortTime = shortTime; + } } diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/directions/util/ConversionUtils.java b/onebusaway-android/src/main/java/org/onebusaway/android/directions/util/ConversionUtils.java index ba5a642fc..5adf1285c 100644 --- a/onebusaway-android/src/main/java/org/onebusaway/android/directions/util/ConversionUtils.java +++ b/onebusaway-android/src/main/java/org/onebusaway/android/directions/util/ConversionUtils.java @@ -239,6 +239,18 @@ public static List fixTimezoneOffsets(List itineraries, } } + public static String getShortFormattedTime(Context context, int offsetGMT, long time) { + DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(context); + timeFormat.setTimeZone(TimeZone.getTimeZone("GMT")); + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + cal.setTimeInMillis(time); + cal.add(Calendar.MILLISECOND, offsetGMT); + if (cal.get(Calendar.SECOND) >= 30) { + cal.add(Calendar.MINUTE, 1); + } + return timeFormat.format(cal.getTime()); + } + public static CharSequence getTimeWithContext(Context applicationContext, int offsetGMT, long time, boolean inLine) { return getTimeWithContext(applicationContext, offsetGMT, time, inLine, -1); diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/directions/util/DirectionExpandableListAdapter.java b/onebusaway-android/src/main/java/org/onebusaway/android/directions/util/DirectionExpandableListAdapter.java index 135c99cda..a62e47842 100644 --- a/onebusaway-android/src/main/java/org/onebusaway/android/directions/util/DirectionExpandableListAdapter.java +++ b/onebusaway-android/src/main/java/org/onebusaway/android/directions/util/DirectionExpandableListAdapter.java @@ -1,222 +1,292 @@ -/* - * Copyright 2012 University of South Florida - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.onebusaway.android.directions.util; - -import org.onebusaway.android.R; -import org.onebusaway.android.directions.model.Direction; - -import android.app.Activity; -import android.content.Context; -import android.graphics.Color; -import android.graphics.Typeface; -import android.text.SpannableString; -import android.text.TextUtils; -import android.text.style.StyleSpan; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseExpandableListAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import java.util.ArrayList; - -import androidx.core.content.ContextCompat; - - -/** - * @author Khoa Tran - */ - -public class DirectionExpandableListAdapter extends BaseExpandableListAdapter { - - Context mContext; - - int mDirectionLayoutResourceId; - - int mSubDirectionLayoutResourceId; - - Direction mData[] = null; - - public DirectionExpandableListAdapter(Context context, int directionLayoutResourceId, - int subDirectionLayoutResourceId, Direction[] data) { - mDirectionLayoutResourceId = directionLayoutResourceId; - mSubDirectionLayoutResourceId = subDirectionLayoutResourceId; - mContext = context; - mData = data; - } - - @Override - public Object getChild(int groupPosition, int childPosition) { - ArrayList subDirections = mData[groupPosition].getSubDirections(); - - if (subDirections != null && !subDirections.isEmpty()) { - return subDirections.get(childPosition); - } - - return null; - } - - @Override - public long getChildId(int groupPosition, int childPosition) { - return childPosition; - } - - @Override - public int getChildrenCount(int groupPosition) { - ArrayList subDirections = mData[groupPosition].getSubDirections(); - if (subDirections != null) { - return subDirections.size(); - } - return 0; - } - - @Override - public View getChildView(int groupPosition, int childPosition, boolean isLastChild, - View convertView, ViewGroup parent) { - - View row = convertView; - DirectionHolder holder = null; - - if (row == null) { - LayoutInflater inflater = ((Activity) mContext).getLayoutInflater(); - row = inflater.inflate(mSubDirectionLayoutResourceId, parent, false); - - holder = new DirectionHolder(); - holder.imgIcon = (ImageView) row.findViewById(R.id.imgIcon); - holder.txtDirection = (TextView) row.findViewById(R.id.directionText); - - row.setTag(holder); - } else { - holder = (DirectionHolder) row.getTag(); - } - - Direction subDirection = (Direction) getChild(groupPosition, childPosition); - CharSequence text = subDirection == null ? "null here" : subDirection.getDirectionText(); - holder.txtDirection.setText(text); - - if (subDirection.getIcon() != -1) { - holder.imgIcon.setImageResource(subDirection.getIcon()); - holder.imgIcon.setColorFilter(Color.GRAY); - } - else { - holder.imgIcon.setVisibility(View.INVISIBLE); - } - return row; - } - - @Override - public Object getGroup(int groupPosition) { - return mData[groupPosition]; - } - - @Override - public int getGroupCount() { - return mData.length; - } - - @Override - public long getGroupId(int groupPosition) { - return groupPosition; - } - - @Override - public View getGroupView(int groupPosition, boolean isExpanded, View convertView, - ViewGroup parent) { - View row = convertView; - DirectionHolder holder = null; - - if (row == null) { - LayoutInflater inflater = ((Activity) mContext).getLayoutInflater(); - row = inflater.inflate(mDirectionLayoutResourceId, parent, false); - - holder = new DirectionHolder(); - holder.imgIcon = (ImageView) row.findViewById(R.id.imgIcon); - holder.noIconText = (TextView) row.findViewById(R.id.noIconText); - holder.txtDirection = (TextView) row.findViewById(R.id.directionText); - - row.setTag(holder); - } else { - holder = (DirectionHolder) row.getTag(); - } - - Direction dir = mData[groupPosition]; - int textColor = mContext.getResources().getColor(R.color.body_text_1); - holder.txtDirection.setTextColor(textColor); - - if (!dir.isTransit()) { - holder.txtDirection.setText(dir.getDirectionIndex() + ". " + dir.getDirectionText()); - holder.imgIcon.setVisibility(View.VISIBLE); - if (dir.getIcon() != -1) { - holder.imgIcon.setImageResource(dir.getIcon()); - holder.imgIcon.setColorFilter(Color.GRAY); - } - else { - holder.imgIcon.setVisibility(View.INVISIBLE); - } - } else { - CharSequence textBeforeTime = dir.getDirectionIndex() + ". " + dir.getService(); - CharSequence text; - CharSequence time = dir.getOldTime(); - text = new SpannableString(textBeforeTime); - if (dir.isRealTimeInfo()) { - if (dir.getNewTime() != null) { - time = dir.getNewTime(); - } - } - text = TextUtils.concat(text, " ", time, "\n", dir.getPlaceAndHeadsign()); - - if (!TextUtils.isEmpty(dir.getAgency())) { - text = TextUtils.concat(text, "\n", dir.getAgency()); - } - if (!TextUtils.isEmpty(dir.getExtra())) { - SpannableString extraSpannableString = new SpannableString(dir.getExtra()); - extraSpannableString.setSpan(new StyleSpan(Typeface.ITALIC), 0, - extraSpannableString.length(), 0); - text = TextUtils.concat(text, "\n", extraSpannableString); - } - - holder.txtDirection.setText(text); - if (dir.getIcon() == -1) { - holder.imgIcon.setVisibility(View.INVISIBLE); - holder.noIconText.setVisibility(View.VISIBLE); - } else { - holder.imgIcon.setVisibility(View.VISIBLE); - holder.imgIcon.setImageResource(dir.getIcon()); - holder.imgIcon.setColorFilter(Color.GRAY); - holder.noIconText.setVisibility(View.INVISIBLE); - } - } - return row; - } - - @Override - public boolean isChildSelectable(int groupPosition, int childPosition) { - return true; - } - - @Override - public boolean hasStableIds() { - return true; - } - - static class DirectionHolder { - ImageView imgIcon; - TextView noIconText; - TextView txtDirection; - } -} \ No newline at end of file +/* + * Copyright 2012 University of South Florida + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onebusaway.android.directions.util; + +import org.onebusaway.android.R; +import org.onebusaway.android.directions.model.Direction; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Color; +import android.graphics.Typeface; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.ArrayList; + +/** + * @author Khoa Tran + */ + +public class DirectionExpandableListAdapter extends BaseExpandableListAdapter { + + Context mContext; + + int mDirectionLayoutResourceId; + + int mSubDirectionLayoutResourceId; + + Direction mData[] = null; + + public DirectionExpandableListAdapter(Context context, int directionLayoutResourceId, + int subDirectionLayoutResourceId, Direction[] data) { + mDirectionLayoutResourceId = directionLayoutResourceId; + mSubDirectionLayoutResourceId = subDirectionLayoutResourceId; + mContext = context; + mData = data; + } + + @Override + public Object getChild(int groupPosition, int childPosition) { + ArrayList subDirections = mData[groupPosition].getSubDirections(); + + if (subDirections != null && !subDirections.isEmpty()) { + return subDirections.get(childPosition); + } + + return null; + } + + @Override + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + @Override + public int getChildrenCount(int groupPosition) { + ArrayList subDirections = mData[groupPosition].getSubDirections(); + if (subDirections != null) { + return subDirections.size(); + } + return 0; + } + + @Override + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, + View convertView, ViewGroup parent) { + + View row = convertView; + DirectionHolder holder = null; + + if (row == null) { + LayoutInflater inflater = ((Activity) mContext).getLayoutInflater(); + row = inflater.inflate(mSubDirectionLayoutResourceId, parent, false); + + holder = new DirectionHolder(); + holder.imgIcon = (ImageView) row.findViewById(R.id.imgIcon); + holder.txtDirection = (TextView) row.findViewById(R.id.directionText); + + row.setTag(holder); + } else { + holder = (DirectionHolder) row.getTag(); + } + + Direction subDirection = (Direction) getChild(groupPosition, childPosition); + CharSequence text = subDirection == null ? "null here" : subDirection.getDirectionText(); + holder.txtDirection.setText(text); + + if (subDirection.getIcon() != -1) { + holder.imgIcon.setImageResource(subDirection.getIcon()); + holder.imgIcon.setColorFilter(Color.GRAY); + } + else { + holder.imgIcon.setVisibility(View.INVISIBLE); + } + return row; + } + + @Override + public Object getGroup(int groupPosition) { + return mData[groupPosition]; + } + + @Override + public int getGroupCount() { + return mData.length; + } + + @Override + public long getGroupId(int groupPosition) { + return groupPosition; + } + + @Override + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, + ViewGroup parent) { + View row = convertView; + DirectionHolder holder = null; + + if (row == null) { + LayoutInflater inflater = ((Activity) mContext).getLayoutInflater(); + row = inflater.inflate(mDirectionLayoutResourceId, parent, false); + + holder = new DirectionHolder(); + holder.imgIcon = (ImageView) row.findViewById(R.id.imgIcon); + holder.noIconText = (TextView) row.findViewById(R.id.noIconText); + holder.txtDirection = (TextView) row.findViewById(R.id.directionText); + holder.transitLeftBlock = (LinearLayout) row.findViewById(R.id.transitLeftBlock); + holder.txtTime = (TextView) row.findViewById(R.id.timeText); + holder.timelineColumn = (LinearLayout) row.findViewById(R.id.timelineColumn); + holder.timelineLineTop = row.findViewById(R.id.timelineLineTop); + holder.timelineLineBottom = row.findViewById(R.id.timelineLineBottom); + holder.timelineDot = row.findViewById(R.id.timelineDot); + holder.rightIcon = (ImageView) row.findViewById(R.id.rightIcon); + + row.setTag(holder); + } else { + holder = (DirectionHolder) row.getTag(); + } + + Direction dir = mData[groupPosition]; + int textColor = mContext.getResources().getColor(R.color.body_text_1); + holder.txtDirection.setTextColor(textColor); + + if (!dir.isTransit()) { + // Non-transit (walk/bike) step + if (holder.transitLeftBlock != null) { + holder.transitLeftBlock.setVisibility(View.GONE); + } + if (holder.txtTime != null) { + holder.txtTime.setVisibility(View.GONE); + } + if (holder.timelineColumn != null) { + holder.timelineColumn.setVisibility(View.GONE); + } + if (holder.rightIcon != null) { + holder.rightIcon.setVisibility(View.GONE); + } + + holder.txtDirection.setText(dir.getDirectionIndex() + ". " + dir.getDirectionText()); + holder.imgIcon.setVisibility(View.VISIBLE); + if (dir.getIcon() != -1) { + holder.imgIcon.setImageResource(dir.getIcon()); + holder.imgIcon.setColorFilter(Color.GRAY); + } else { + holder.imgIcon.setVisibility(View.INVISIBLE); + } + if (holder.noIconText != null) { + holder.noIconText.setVisibility(View.GONE); + } + } else { + // Transit step — show time, timeline, right icon + if (holder.transitLeftBlock != null) { + holder.transitLeftBlock.setVisibility(View.VISIBLE); + } + holder.imgIcon.setVisibility(View.GONE); + if (holder.noIconText != null) { + holder.noIconText.setVisibility(View.GONE); + } + + // Short time on the left (e.g., "12:22 AM") + CharSequence shortTime = dir.getShortTime(); + if (holder.txtTime != null && !TextUtils.isEmpty(shortTime)) { + holder.txtTime.setVisibility(View.VISIBLE); + holder.txtTime.setText(shortTime); + } else if (holder.txtTime != null) { + holder.txtTime.setVisibility(View.GONE); + } + + // Timeline dot and connecting lines + if (holder.timelineColumn != null) { + holder.timelineColumn.setVisibility(View.VISIBLE); + + boolean prevIsTransit = groupPosition > 0 + && mData[groupPosition - 1].isTransit(); + boolean nextIsTransit = groupPosition < mData.length - 1 + && mData[groupPosition + 1].isTransit(); + + holder.timelineLineTop.setVisibility( + prevIsTransit ? View.VISIBLE : View.INVISIBLE); + holder.timelineLineBottom.setVisibility( + nextIsTransit ? View.VISIBLE : View.INVISIBLE); + } + + // Transit mode icon on the right — for "Alight" steps (icon == -1), + // inherit the icon from the preceding "Board" step + int transitIcon = dir.getIcon(); + if (transitIcon == -1 && groupPosition > 0 + && mData[groupPosition - 1].isTransit()) { + transitIcon = mData[groupPosition - 1].getIcon(); + } + if (holder.rightIcon != null) { + if (transitIcon != -1) { + holder.rightIcon.setVisibility(View.VISIBLE); + holder.rightIcon.setImageResource(transitIcon); + holder.rightIcon.setColorFilter(Color.GRAY); + } else { + holder.rightIcon.setVisibility(View.GONE); + } + } + + // Bold service label + SpannableString serviceSpan = new SpannableString(dir.getService()); + serviceSpan.setSpan(new StyleSpan(Typeface.BOLD), 0, serviceSpan.length(), 0); + + CharSequence text = serviceSpan; + text = TextUtils.concat(text, "\n", dir.getPlaceAndHeadsign()); + + if (!TextUtils.isEmpty(dir.getAgency())) { + text = TextUtils.concat(text, "\n", dir.getAgency()); + } + if (!TextUtils.isEmpty(dir.getExtra())) { + SpannableString extraSpan = new SpannableString(dir.getExtra()); + extraSpan.setSpan(new StyleSpan(Typeface.ITALIC), 0, + extraSpan.length(), 0); + extraSpan.setSpan( + new ForegroundColorSpan( + mContext.getResources().getColor(R.color.header_stop_info_ontime)), + 0, extraSpan.length(), 0); + text = TextUtils.concat(text, "\n", extraSpan); + } + + holder.txtDirection.setText(text); + } + return row; + } + + @Override + public boolean isChildSelectable(int groupPosition, int childPosition) { + return true; + } + + @Override + public boolean hasStableIds() { + return true; + } + + static class DirectionHolder { + ImageView imgIcon; + TextView noIconText; + TextView txtDirection; + LinearLayout transitLeftBlock; + TextView txtTime; + LinearLayout timelineColumn; + View timelineLineTop; + View timelineDot; + View timelineLineBottom; + ImageView rightIcon; + } +} diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/directions/util/DirectionsGenerator.java b/onebusaway-android/src/main/java/org/onebusaway/android/directions/util/DirectionsGenerator.java index b3bbf15e6..9aecf45c3 100644 --- a/onebusaway-android/src/main/java/org/onebusaway/android/directions/util/DirectionsGenerator.java +++ b/onebusaway-android/src/main/java/org/onebusaway/android/directions/util/DirectionsGenerator.java @@ -550,6 +550,11 @@ public Direction generateTransitSubdirection(Leg leg, boolean isOnDirection) { oldTime.getTimeInMillis(), true)); direction.setOldTime(oldTimeString); + long displayTimeMs = leg.realTime ? newTime.getTimeInMillis() + : oldTime.getTimeInMillis(); + direction.setShortTime(ConversionUtils.getShortFormattedTime( + applicationContext, leg.agencyTimeZoneOffset, displayTimeMs)); + return direction; } diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/directions/util/TripRequestBuilder.java b/onebusaway-android/src/main/java/org/onebusaway/android/directions/util/TripRequestBuilder.java index 84468aaec..f47a270d3 100644 --- a/onebusaway-android/src/main/java/org/onebusaway/android/directions/util/TripRequestBuilder.java +++ b/onebusaway-android/src/main/java/org/onebusaway/android/directions/util/TripRequestBuilder.java @@ -246,6 +246,9 @@ public TripRequest execute(Activity activity) { request.getParameters().put("mode", modeString); } + // Request more itineraries than the OTP default of 3 + request.getParameters().put("numItineraries", "5"); + // Our default. This could be configurable. request.setShowIntermediateStops(true); diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/TripPlanFragment.java b/onebusaway-android/src/main/java/org/onebusaway/android/ui/TripPlanFragment.java index f9194e9af..e33f93234 100644 --- a/onebusaway-android/src/main/java/org/onebusaway/android/ui/TripPlanFragment.java +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/TripPlanFragment.java @@ -20,11 +20,8 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.res.TypedArray; -import android.database.Cursor; import android.location.Location; -import android.net.Uri; import android.os.Bundle; -import android.provider.ContactsContract; import android.text.TextUtils; import android.text.format.DateFormat; import android.util.Log; @@ -45,11 +42,6 @@ import android.widget.TextView; import android.widget.Toast; -import androidx.activity.result.ActivityResultCallback; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContract; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; @@ -58,6 +50,7 @@ import com.google.android.gms.common.GoogleApiAvailability; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.material.button.MaterialButton; import com.google.android.material.datepicker.MaterialDatePicker; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.timepicker.MaterialTimePicker; @@ -116,9 +109,8 @@ interface Listener { private AutoCompleteTextView mFromAddressTextArea; private AutoCompleteTextView mToAddressTextArea; private ImageButton mFromCurrentLocationImageButton; - private ImageButton mToCurrentLocationImageButton; - private ImageButton mfromContactsImageButton; - private ImageButton mToContactsImageButton; + private ImageButton mSwapButton; + private MaterialButton mSearchButton; private Spinner mDate; private ArrayAdapter mDateAdapter; private Spinner mTime; @@ -142,87 +134,6 @@ interface Listener { private FirebaseAnalytics mFirebaseAnalytics; - // Updates the Address Input field with the formatted address selected by the user from their contacts. - private final String ADDRESS_INPUT_ID_KEY = "addressInputId"; - private final ActivityResultContract selectAddressFromContactContract = new ActivityResultContract() { - private int addressInputId; - - @NonNull - @Override - public Intent createIntent(@NonNull Context context, TextView addressInput) { - Intent pickContactIntent = new Intent(Intent.ACTION_PICK); - pickContactIntent.setType(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_TYPE); - addressInputId = addressInput.getId(); - return pickContactIntent; - } - - @Override - public Intent parseResult(int i, @Nullable Intent addressIntent) { - if (addressIntent != null) { - return addressIntent.putExtra(ADDRESS_INPUT_ID_KEY, addressInputId); - } - return null; - } - }; - - private final ActivityResultCallback addressIntentActivityResultCallback = addressIntent -> { - if (addressIntent == null) { - return; - } - - Uri addressUri = addressIntent.getData(); - if (addressUri == null) { - return; - } - - String[] projection = {ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS}; - - try (Cursor cursor = getContext().getContentResolver().query(addressUri, projection, null, null, null)) { - if (cursor == null || !cursor.moveToFirst()) { - return; - } - - String address = extractAddress(cursor); - int addressInputId = addressIntent.getIntExtra(ADDRESS_INPUT_ID_KEY, -1); - if (addressInputId == -1) { - return; - } - - updateAddressInput(address, addressInputId); - updateAddressData(address, addressInputId); - } - }; - - private String extractAddress(Cursor cursor) { - int addressIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS); - return cursor.getString(addressIndex).replace("\n", ", "); - } - - private void updateAddressInput(String address, int addressInputId) { - TextView addressInput = getActivity().findViewById(addressInputId); - addressInput.post(() -> addressInput.setText(address)); - addressInput.requestFocus(); - } - - private void updateAddressData(String address, int addressInputId) { - CustomAddress customAddress = CustomAddress.getEmptyAddress(); - customAddress.setAddressLine(0, address); - - if (addressInputId == mFromAddressTextArea.getId()) { - mFromAddress = customAddress; - mBuilder.setFrom(mFromAddress); - } else if (addressInputId == mToAddressTextArea.getId()) { - mToAddress = customAddress; - mBuilder.setTo(mToAddress); - } - } - - - private final ActivityResultLauncher mSelectAddressFromContactLauncher = registerForActivityResult( - selectAddressFromContactContract, - addressIntentActivityResultCallback - ); - // Create view, initialize state @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -247,9 +158,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa mFromAddressTextArea = (AutoCompleteTextView) view.findViewById(R.id.fromAddressTextArea); mToAddressTextArea = (AutoCompleteTextView) view.findViewById(R.id.toAddressTextArea); mFromCurrentLocationImageButton = (ImageButton) view.findViewById(R.id.fromCurrentLocationImageButton); - mToCurrentLocationImageButton = (ImageButton) view.findViewById(R.id.toCurrentLocationImageButton); - mfromContactsImageButton = view.findViewById(R.id.fromContactsImageButton); - mToContactsImageButton = view.findViewById(R.id.toContactsImageButton); + mSwapButton = (ImageButton) view.findViewById(R.id.swapImageButton); + mSearchButton = (MaterialButton) view.findViewById(R.id.tripPlanSearchButton); mDate = (Spinner) view.findViewById(R.id.date); mDateAdapter = new ArrayAdapter(getActivity(), R.layout.simple_list_item); mDate.setAdapter(mDateAdapter); @@ -291,7 +201,6 @@ public boolean onTouch(View view, MotionEvent motionEvent) { resetDateTimeLabels(); mBuilder.setDateTime(mMyCalendar); - checkRequestAndSubmit(); }); materialDatePicker.show(getChildFragmentManager(), "DATE_PICKER"); @@ -322,7 +231,6 @@ public boolean onTouch(View view, MotionEvent motionEvent) { resetDateTimeLabels(); mBuilder.setDateTime(mMyCalendar); - checkRequestAndSubmit(); }); materialTimePicker.show(getChildFragmentManager(), "MATERIAL_TIME_PICKER"); return true; @@ -339,36 +247,34 @@ public void onClick(View v) { mMyCalendar = Calendar.getInstance(); mBuilder.setDateTime(mMyCalendar); resetDateTimeLabels(); - checkRequestAndSubmit(); } }); setUpAutocomplete(mFromAddressTextArea, USE_FROM_ADDRESS); setUpAutocomplete(mToAddressTextArea, USE_TO_ADDRESS); - mToCurrentLocationImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mToAddressTextArea.setText(getString(R.string.tripplanner_current_location)); - mToAddress = makeAddressFromLocation(); - mBuilder.setTo(mToAddress); - checkRequestAndSubmit(); - } - }); - mFromCurrentLocationImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mFromAddressTextArea.setText(getString(R.string.tripplanner_current_location)); mFromAddress = makeAddressFromLocation(); mBuilder.setFrom(mFromAddress); - checkRequestAndSubmit(); } }); - mToContactsImageButton.setOnClickListener(v -> mSelectAddressFromContactLauncher.launch(mToAddressTextArea)); + mSwapButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + swapOriginAndDestination(); + } + }); - mfromContactsImageButton.setOnClickListener(v -> mSelectAddressFromContactLauncher.launch(mFromAddressTextArea)); + mSearchButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + checkRequestAndSubmit(); + } + }); // Start: default from address is Current Location, to address is unset return view; @@ -477,7 +383,6 @@ public void onItemSelected(AdapterView parent, View view, int position, long } else { mBuilder.setDepartureTime(mMyCalendar); } - checkRequestAndSubmit(); } @Override @@ -525,7 +430,7 @@ public boolean onOptionsItemSelected(MenuItem item) { advancedSettings(); return true; } else if (id == R.id.action_reverse) { - reverseTrip(); + swapOriginAndDestination(); return true; } else if (id == R.id.action_report_trip_problem) { reportTripPlanProblem(); @@ -600,8 +505,6 @@ public void onClick(DialogInterface dialogInterface, int which) { PreferenceUtils.saveDouble(getString(R.string.preference_key_trip_plan_maximum_walking_distance), maxWalkDistance); PreferenceUtils.saveBoolean(getString(R.string.preference_key_trip_plan_minimize_transfers), optimizeTransfers); PreferenceUtils.saveBoolean(getString(R.string.preference_key_trip_plan_avoid_stairs), wheelchair); - - checkRequestAndSubmit(); } }); @@ -652,18 +555,17 @@ public void onClick(DialogInterface dialogInterface, int which) { } } - private void reverseTrip() { - mFromAddress = mBuilder.getTo(); - mToAddress = mBuilder.getFrom(); + private void swapOriginAndDestination() { + CustomAddress tempFrom = mBuilder.getTo(); + CustomAddress tempTo = mBuilder.getFrom(); + + mFromAddress = tempFrom; + mToAddress = tempTo; mBuilder.setFrom(mFromAddress).setTo(mToAddress); setAddressText(mFromAddressTextArea, mFromAddress); setAddressText(mToAddressTextArea, mToAddress); - - if (mBuilder.ready() && mListener != null) { - mListener.onTripRequestReady(); - } } private void reportTripPlanProblem() { @@ -741,8 +643,6 @@ public void onActivityResult(int requestCode, int resultCode, Intent intent) { mBuilder.setTo(mToAddress); mToAddressTextArea.setText(mToAddress.toString()); } - - checkRequestAndSubmit(); } private void setUpAutocomplete(AutoCompleteTextView tv, final int use) { @@ -768,10 +668,7 @@ private void setUpAutocomplete(AutoCompleteTextView tv, final int use) { mToAddress = addr; mBuilder.setTo(mToAddress); } - - checkRequestAndSubmit(); }); tv.dismissDropDown(); } } - diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/TripResultsFragment.java b/onebusaway-android/src/main/java/org/onebusaway/android/ui/TripResultsFragment.java index c780ad51a..3f8a68558 100755 --- a/onebusaway-android/src/main/java/org/onebusaway/android/ui/TripResultsFragment.java +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/TripResultsFragment.java @@ -15,25 +15,12 @@ */ package org.onebusaway.android.ui; -import com.google.android.material.tabs.TabLayout; - -import org.onebusaway.android.R; -import org.onebusaway.android.app.Application; -import org.onebusaway.android.directions.model.Direction; -import org.onebusaway.android.directions.realtime.RealtimeService; -import org.onebusaway.android.directions.util.ConversionUtils; -import org.onebusaway.android.directions.util.DirectionExpandableListAdapter; -import org.onebusaway.android.directions.util.DirectionsGenerator; -import org.onebusaway.android.directions.util.OTPConstants; -import org.onebusaway.android.map.MapParams; -import org.onebusaway.android.map.ObaMapFragment; -import org.opentripplanner.api.model.Itinerary; - import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.content.Intent; import android.graphics.PorterDuff; +import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; @@ -43,19 +30,39 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ExpandableListView; +import android.widget.HorizontalScrollView; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; +import androidx.annotation.DrawableRes; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; + +import java.util.LinkedHashSet; + +import com.google.android.material.tabs.TabLayout; + +import org.onebusaway.android.R; +import org.onebusaway.android.app.Application; +import org.onebusaway.android.directions.model.Direction; +import org.onebusaway.android.directions.realtime.RealtimeService; +import org.onebusaway.android.directions.util.DirectionExpandableListAdapter; +import org.onebusaway.android.directions.util.DirectionsGenerator; +import org.onebusaway.android.directions.util.OTPConstants; +import org.onebusaway.android.map.MapParams; +import org.onebusaway.android.map.ObaMapFragment; +import org.opentripplanner.api.model.Itinerary; +import org.opentripplanner.api.model.Leg; +import org.opentripplanner.routing.core.TraverseMode; +import org.opentripplanner.routing.core.TraverseModeSet; + import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Locale; -import androidx.annotation.DrawableRes; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; - public class TripResultsFragment extends Fragment { private static final String TAG = "TripResultsFragment"; @@ -69,7 +76,9 @@ public class TripResultsFragment extends Fragment { private View mMapFragmentFrame; private boolean mShowingMap = false; - private RoutingOptionPicker[] mOptions = new RoutingOptionPicker[3]; + private HorizontalScrollView mOptionsScrollView; + private LinearLayout mOptionsContainer; + private int mSelectedIndex = 0; private Listener mListener; @@ -101,9 +110,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa mDirectionsListView = (ExpandableListView) view.findViewById(R.id.directionsListView); mMapFragmentFrame = view.findViewById(R.id.mapFragment); - mOptions[0] = new RoutingOptionPicker(view, R.id.option1LinearLayout, R.id.option1Title, R.id.option1Duration, R.id.option1Interval); - mOptions[1] = new RoutingOptionPicker(view, R.id.option2LinearLayout, R.id.option2Title, R.id.option2Duration, R.id.option2Interval); - mOptions[2] = new RoutingOptionPicker(view, R.id.option3LinearLayout, R.id.option3Title, R.id.option3Duration, R.id.option3Interval); + mOptionsScrollView = view.findViewById(R.id.tripOptionsScrollView); + mOptionsContainer = view.findViewById(R.id.tripOptionsContainer); int rank = getArguments().getInt(OTPConstants.SELECTED_ITINERARY); // defaults to 0 mShowingMap = getArguments().getBoolean(OTPConstants.SHOW_MAP); @@ -224,7 +232,9 @@ private void showMap(boolean show) { mShowingMap = show; if (show) { mMapFragmentFrame.bringToFront(); - mMapFragment.setMapMode(MapParams.MODE_DIRECTIONS, mMapBundle); + if (mMapFragment != null) { + mMapFragment.setMapMode(MapParams.MODE_DIRECTIONS, mMapBundle); + } } else { mDirectionsListView.bringToFront(); } @@ -233,14 +243,21 @@ private void showMap(boolean show) { } private void initInfoAndMap(int trip) { + List itineraries = getItineraries(); + if (itineraries == null || itineraries.isEmpty()) { + return; + } + + if (trip >= itineraries.size()) { + trip = 0; + } initMap(trip); - for (int i = 0; i < mOptions.length; i++) { - mOptions[i].setItinerary(i); - } + mSelectedIndex = trip; + populateTripOptions(itineraries); - mOptions[trip].select(); + selectOption(trip); showMap(mShowingMap); } @@ -257,6 +274,20 @@ private String toDateFmt(long ms) { return s.substring(0, 6).toLowerCase(); } + private String toCompactTime(long ms) { + return new SimpleDateFormat("h:mm a", Locale.getDefault()).format(new Date(ms)); + } + + private String toCompactDuration(double durationSec) { + long totalMin = (long) (durationSec / 60); + long h = totalMin / 60; + long m = totalMin % 60; + if (h > 0) { + return h + "h " + m + "m"; + } + return m + "m"; + } + private String formatTimeString(String ms, double durationSec) { long start = Long.parseLong(ms); String fromString = toDateFmt(start); @@ -268,106 +299,185 @@ private List getItineraries() { return (List) getArguments().getSerializable(OTPConstants.ITINERARIES); } - private class RoutingOptionPicker { - LinearLayout linearLayout; - TextView titleView; - TextView durationView; - TextView intervalView; - - Itinerary itinerary; - int rank; - - RoutingOptionPicker(View view, int linearLayout, int titleView, int durationView, int intervalView) { - this.linearLayout = (LinearLayout) view.findViewById(linearLayout); - this.titleView = (TextView) view.findViewById(titleView); - this.durationView = (TextView) view.findViewById(durationView); - this.intervalView = (TextView) view.findViewById(intervalView); - - this.linearLayout.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - RoutingOptionPicker.this.select(); - } - }); + private void selectOption(int position) { + if (position < 0) { + return; } - void select() { - int defaultTextColor = getResources().getColor(R.color.header_text_color); - int selectedTextColor = getResources().getColor(R.color.trip_plan_header_text_selected); + List itineraries = getItineraries(); + if (itineraries == null || position >= itineraries.size()) { + return; + } - for (RoutingOptionPicker picker : mOptions) { - // reset - picker.linearLayout.setBackgroundColor(getResources().getColor(R.color.trip_plan_card_background)); - picker.titleView.setTextColor(defaultTextColor); - picker.durationView.setTextColor(getResources().getColor(R.color.header_text_faded_color)); - picker.intervalView.setTextColor(getResources().getColor(R.color.header_text_faded_color)); - } + mSelectedIndex = position; + getArguments().putInt(OTPConstants.SELECTED_ITINERARY, position); - // select - linearLayout.setBackgroundResource(R.drawable.trip_option_selected_item); - titleView.setTextColor(selectedTextColor); - durationView.setTextColor(selectedTextColor); - intervalView.setTextColor(selectedTextColor); + updateOptionStyles(itineraries); + scrollToSelectedCard(position); - getArguments().putInt(OTPConstants.SELECTED_ITINERARY, rank); + Itinerary itinerary = itineraries.get(position); + updateInfo(itinerary); + updateMap(itinerary); + } - updateInfo(); - updateMap(); + private void scrollToSelectedCard(int position) { + if (mOptionsScrollView == null || mOptionsContainer == null) { + return; } + if (position < 0 || position >= mOptionsContainer.getChildCount()) { + return; + } + View card = mOptionsContainer.getChildAt(position); + card.post(() -> mOptionsScrollView.smoothScrollTo(card.getLeft(), 0)); + } + private void updateInfo(Itinerary itinerary) { + DirectionsGenerator gen = new DirectionsGenerator(itinerary.legs, getActivity().getApplicationContext()); + List directions = gen.getDirections(); + Direction[] directionData = directions.toArray(new Direction[0]); - void setItinerary(int rank) { - List trips = getItineraries(); - if (rank >= trips.size()) { - this.itinerary = null; - linearLayout.setVisibility(View.GONE); - return; - } + DirectionExpandableListAdapter adapter = new DirectionExpandableListAdapter( + getActivity(), + R.layout.list_direction_item, R.layout.list_subdirection_item, directionData); - this.itinerary = trips.get(rank); - this.rank = rank; + mDirectionsListView.setAdapter(adapter); + mDirectionsListView.setGroupIndicator(null); - String title = new DirectionsGenerator(itinerary.legs, getContext()).getItineraryTitle(); - String duration = ConversionUtils.getFormattedDurationTextNoSeconds(itinerary.duration, false, getContext()); - String interval = formatTimeString(itinerary.startTime, itinerary.duration * 1000); + Context context = Application.get().getApplicationContext(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + NotificationChannel channel = manager.getNotificationChannel(Application.CHANNEL_TRIP_PLAN_UPDATES_ID); + if (channel.getImportance() != NotificationManager.IMPORTANCE_NONE) { + RealtimeService.start(getActivity(), getArguments()); + } + } else { + if (Application.getPrefs() + .getBoolean(getString(R.string.preference_key_trip_plan_notifications), true)) { + RealtimeService.start(getActivity(), getArguments()); + } + } + } - titleView.setText(title); - durationView.setText(duration); - intervalView.setText(interval); + private void updateMap(Itinerary itinerary) { + mMapBundle.putSerializable(MapParams.ITINERARY, itinerary); + if (mMapFragment != null) { + mMapFragment.setMapMode(MapParams.MODE_DIRECTIONS, mMapBundle); } + } - void updateInfo() { - DirectionsGenerator gen = new DirectionsGenerator(itinerary.legs, getActivity().getApplicationContext()); - List directions = gen.getDirections(); - Direction direction_data[] = directions.toArray(new Direction[directions.size()]); + private void populateTripOptions(List itineraries) { + mOptionsContainer.removeAllViews(); + Context ctx = getActivity(); + if (ctx == null) return; - DirectionExpandableListAdapter adapter = new DirectionExpandableListAdapter( - getActivity(), - R.layout.list_direction_item, R.layout.list_subdirection_item, direction_data); + int screenWidth = getResources().getDisplayMetrics().widthPixels; + int cardWidth = (int) (screenWidth * 0.72); + float density = getResources().getDisplayMetrics().density; + int iconSize = (int) (18 * density); - mDirectionsListView.setAdapter(adapter); + for (int i = 0; i < itineraries.size(); i++) { + Itinerary itinerary = itineraries.get(i); + View card = LayoutInflater.from(ctx).inflate(R.layout.item_trip_option, mOptionsContainer, false); - mDirectionsListView.setGroupIndicator(null); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + cardWidth, LinearLayout.LayoutParams.MATCH_PARENT); + lp.setMarginEnd((int) (4 * density)); + card.setLayoutParams(lp); - Context context = Application.get().getApplicationContext(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - NotificationChannel channel = manager.getNotificationChannel(Application.CHANNEL_TRIP_PLAN_UPDATES_ID); - if(channel.getImportance() != NotificationManager.IMPORTANCE_NONE){ - RealtimeService.start(getActivity(), getArguments()); - } - } else { - if (Application.getPrefs() - .getBoolean(getString(R.string.preference_key_trip_plan_notifications), true)) { + bindOptionCard(card, itinerary, i, ctx, iconSize); + mOptionsContainer.addView(card); + } + } - RealtimeService.start(getActivity(), getArguments()); - } + private void bindOptionCard(View card, Itinerary itinerary, int position, + Context ctx, int iconSize) { + LinearLayout linearLayout = card.findViewById(R.id.tripOptionLinearLayout); + LinearLayout modeChainLayout = card.findViewById(R.id.tripOptionModeChain); + TextView titleView = card.findViewById(R.id.tripOptionTitle); + View dividerView = card.findViewById(R.id.tripOptionDivider); + TextView etaView = card.findViewById(R.id.tripOptionEta); + TextView durationView = card.findViewById(R.id.tripOptionDuration); + + DirectionsGenerator gen = new DirectionsGenerator(itinerary.legs, ctx); + String title = gen.getItineraryTitle(); + String duration = toCompactDuration(itinerary.duration); + + long startMs = Long.parseLong(itinerary.startTime); + long endMs = startMs + (long) (itinerary.duration * 1000); + String eta = "ETA " + toCompactTime(endMs); + + boolean isSelected = (position == mSelectedIndex); + int selectedTextColor = getResources().getColor(R.color.trip_plan_header_text_selected); + int defaultTextColor = getResources().getColor(R.color.header_text_color); + int fadedTextColor = getResources().getColor(R.color.header_text_faded_color); + int textColor = isSelected ? selectedTextColor : defaultTextColor; + int iconColor = isSelected ? selectedTextColor + : getResources().getColor(R.color.trip_option_icon_tint); + + modeChainLayout.removeAllViews(); + LinkedHashSet seenModes = new LinkedHashSet<>(); + for (Leg leg : itinerary.legs) { + if (seenModes.contains(leg.mode)) continue; + seenModes.add(leg.mode); + + TraverseMode mode = TraverseMode.valueOf(leg.mode); + int modeRes = DirectionsGenerator.getModeIcon(new TraverseModeSet(mode)); + if (modeRes == -1) continue; + + if (modeChainLayout.getChildCount() > 0) { + TextView sep = new TextView(ctx); + sep.setText(" > "); + sep.setTextSize(14); + sep.setTextColor(iconColor); + modeChainLayout.addView(sep); } + ImageView iv = new ImageView(ctx); + iv.setImageResource(modeRes); + iv.setLayoutParams(new LinearLayout.LayoutParams(iconSize, iconSize)); + iv.setColorFilter(iconColor); + modeChainLayout.addView(iv); } - void updateMap() { - mMapBundle.putSerializable(MapParams.ITINERARY, itinerary); - mMapFragment.setMapMode(MapParams.MODE_DIRECTIONS, mMapBundle); + // Duration bold and large, right after icons + TextView durationInChain = new TextView(ctx); + durationInChain.setText(" " + duration); + durationInChain.setTextSize(16); + durationInChain.setTypeface(null, Typeface.BOLD); + durationInChain.setTextColor(textColor); + modeChainLayout.addView(durationInChain); + + titleView.setText(title); + etaView.setText(eta); + durationView.setText(duration + " travel"); + + int dividerColor = 0x40808080; + if (isSelected) { + linearLayout.setBackgroundResource(R.drawable.trip_option_selected_item); + titleView.setTextColor(selectedTextColor); + etaView.setTextColor(selectedTextColor); + durationView.setTextColor(selectedTextColor); + } else { + linearLayout.setBackgroundColor(getResources().getColor(R.color.trip_plan_card_background)); + titleView.setTextColor(fadedTextColor); + etaView.setTextColor(fadedTextColor); + durationView.setTextColor(fadedTextColor); + } + dividerView.setBackgroundColor(dividerColor); + + card.setOnClickListener(v -> selectOption(position)); + } + + private void updateOptionStyles(List itineraries) { + if (mOptionsContainer == null || itineraries == null) return; + Context ctx = getActivity(); + if (ctx == null) return; + + float density = getResources().getDisplayMetrics().density; + int iconSize = (int) (18 * density); + + for (int i = 0; i < mOptionsContainer.getChildCount() && i < itineraries.size(); i++) { + View card = mOptionsContainer.getChildAt(i); + bindOptionCard(card, itineraries.get(i), i, ctx, iconSize); } } } diff --git a/onebusaway-android/src/main/res/drawable/ic_reset_time.xml b/onebusaway-android/src/main/res/drawable/ic_reset_time.xml new file mode 100644 index 000000000..9a837e815 --- /dev/null +++ b/onebusaway-android/src/main/res/drawable/ic_reset_time.xml @@ -0,0 +1,9 @@ + + + diff --git a/onebusaway-android/src/main/res/drawable/ic_swap_vert.xml b/onebusaway-android/src/main/res/drawable/ic_swap_vert.xml new file mode 100644 index 000000000..7196f99e0 --- /dev/null +++ b/onebusaway-android/src/main/res/drawable/ic_swap_vert.xml @@ -0,0 +1,9 @@ + + + diff --git a/onebusaway-android/src/main/res/drawable/ic_timeline_dot.xml b/onebusaway-android/src/main/res/drawable/ic_timeline_dot.xml new file mode 100644 index 000000000..66a913ac9 --- /dev/null +++ b/onebusaway-android/src/main/res/drawable/ic_timeline_dot.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/onebusaway-android/src/main/res/layout/fragment_trip_plan.xml b/onebusaway-android/src/main/res/layout/fragment_trip_plan.xml index 72bb25c5f..717913362 100755 --- a/onebusaway-android/src/main/res/layout/fragment_trip_plan.xml +++ b/onebusaway-android/src/main/res/layout/fragment_trip_plan.xml @@ -28,13 +28,15 @@ + android:layout_height="wrap_content" + android:layout_toLeftOf="@id/swapImageButton" + android:layout_toStartOf="@id/swapImageButton"> - - - + + + android:layout_below="@id/trip_plan_from_layout" + android:layout_toLeftOf="@id/swapImageButton" + android:layout_toStartOf="@id/swapImageButton"> @@ -111,37 +110,6 @@ android:singleLine="true" android:requiresFadingEdge="horizontal" /> - - - - @@ -233,8 +201,8 @@ android:padding="2dp" android:scaleType="fitXY" android:id="@+id/resetTimeImageButton" - android:src="@drawable/ic_arrival_time" - android:background="#00000000" + android:src="@drawable/ic_reset_time" + android:background="@android:color/transparent" android:layout_gravity="center_vertical" android:contentDescription="@string/reset_time" app:tint="@color/material_gray" /> @@ -242,4 +210,12 @@ + + diff --git a/onebusaway-android/src/main/res/layout/fragment_trip_plan_results.xml b/onebusaway-android/src/main/res/layout/fragment_trip_plan_results.xml index 7c9b860d6..47605c09f 100755 --- a/onebusaway-android/src/main/res/layout/fragment_trip_plan_results.xml +++ b/onebusaway-android/src/main/res/layout/fragment_trip_plan_results.xml @@ -14,149 +14,26 @@ limitations under the License. --> - + android:layout_margin="4dp" + android:scrollbars="none"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onebusaway-android/src/main/res/layout/list_direction_item.xml b/onebusaway-android/src/main/res/layout/list_direction_item.xml index 8d29d03ea..c6e8183ee 100644 --- a/onebusaway-android/src/main/res/layout/list_direction_item.xml +++ b/onebusaway-android/src/main/res/layout/list_direction_item.xml @@ -1,53 +1,125 @@ - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onebusaway-android/src/main/res/values-es/strings.xml b/onebusaway-android/src/main/res/values-es/strings.xml index bf31e17e2..7b0befe8a 100644 --- a/onebusaway-android/src/main/res/values-es/strings.xml +++ b/onebusaway-android/src/main/res/values-es/strings.xml @@ -29,7 +29,7 @@ Ayuda Enviar comentarios Visitanos en GitHub - Planea un Viaje (beta) + Planea un Viaje Pagar mi pasaje @@ -612,7 +612,7 @@ muestra la información de llegadas en el encabezado en la parte superior de la pantalla (esto no afecta en la vista del mapa) - Notificaciones del Planeador de Viajes (beta) + Notificaciones del Planeador de Viajes Después de que planeas un viaje, enviar notificaciones sobre si el viaje esta atrasado o ya no es recomendado. @@ -932,7 +932,7 @@ Acerca de - Planeador de Viajes (beta) + Planeador de Viajes Origen: Destino: Saliendo diff --git a/onebusaway-android/src/main/res/values-fi/strings.xml b/onebusaway-android/src/main/res/values-fi/strings.xml index a11bf6346..f4a990703 100644 --- a/onebusaway-android/src/main/res/values-fi/strings.xml +++ b/onebusaway-android/src/main/res/values-fi/strings.xml @@ -26,7 +26,7 @@ Ohjeet Lähetä palautetta Vieraile GitHubissa - Suunnittele matka (beta) + Suunnittele matka Maksa matkalippu Tähdellä merkityt linjat @@ -599,7 +599,7 @@ Salli %1$s-ilmoitusten värinä Muistutusääni Valitse %1$s-ilmoitusääni - Matkasuunnitelmailmoitukset (beta) + Matkasuunnitelmailmoitukset Matkan suunnittelun jälkeen lähetä ilmoitukset, jos matka on myöhässä tai sitä ei enää suositella. Lähetä anonyymit matkakäyttäytymistiedot Auta meitä parantamaan julkista liikennettä @@ -962,7 +962,7 @@ EI - Matkasuunnittelu (beta) + Matkasuunnittelu Mistä: Minne: Lähtö diff --git a/onebusaway-android/src/main/res/values-it/strings.xml b/onebusaway-android/src/main/res/values-it/strings.xml index c6f42d674..7f3fefce5 100644 --- a/onebusaway-android/src/main/res/values-it/strings.xml +++ b/onebusaway-android/src/main/res/values-it/strings.xml @@ -28,7 +28,7 @@ Aiuto Invia feedback Scoprici su GitHub! - Pianifica un viaggio (beta) + Pianifica un viaggio Compra biglietto @@ -511,7 +511,7 @@ Mostra arrivi sopra nome fermata Quando visualizzi i preferiti, mostra informazioni sugli arrivi nell\'intestazione (la visuale della mappa resta invariata) - Notifiche sul piano di viaggio (beta) + Notifiche sul piano di viaggio Dopo aver pianificato un viaggio, invia notifiche se ci sono ritardi o se il viaggio non è più consigliato. Mostra schermate tutorial @@ -787,7 +787,7 @@ Informazioni - Pianificatore di viaggio (beta) + Pianificatore di viaggio Da: A: Partenza diff --git a/onebusaway-android/src/main/res/values-pl/strings.xml b/onebusaway-android/src/main/res/values-pl/strings.xml index 98acd0fcd..efc75a686 100644 --- a/onebusaway-android/src/main/res/values-pl/strings.xml +++ b/onebusaway-android/src/main/res/values-pl/strings.xml @@ -11,7 +11,7 @@ Wyślij opinię Ulubione linie Odwiedź nas na GitHubie - Zaplanuj podróż (beta) + Zaplanuj podróż Zapłać za bilet @@ -640,7 +640,7 @@ pokaż przyjazdy w nagłówku u góry ekranu (nie wpływa na widok mapy) - Powiadomienia o planowaniu podróży (beta) + Powiadomienia o planowaniu podróży Po zaplanowaniu podróży wysyłaj powiadomienia, jeśli podróż jest opóźniona lub nie jest już zalecana. @@ -1038,7 +1038,7 @@ Przeglądaj sieć - Planer podróży (beta) + Planer podróży Skąd: Dokąd: Wyjazd diff --git a/onebusaway-android/src/main/res/values/strings.xml b/onebusaway-android/src/main/res/values/strings.xml index d61bc3c73..b59a39e5c 100644 --- a/onebusaway-android/src/main/res/values/strings.xml +++ b/onebusaway-android/src/main/res/values/strings.xml @@ -28,7 +28,7 @@ Help Send feedback Visit us on GitHub - Plan a trip (beta) + Plan a trip Pay my fare @@ -669,7 +669,7 @@ show arrival info in the header at the top of the screen (doesn\'t affect map view) - Trip plan notifications (beta) + Trip plan notifications After planning a trip, send notifications if the trip is delayed or no longer recommended. @@ -1006,7 +1006,7 @@ - Trip Planner (beta) + Trip Planner From: To: Leaving @@ -1069,8 +1069,8 @@ - Get on - Get off + Board + Alight - tram - subway - rail - bus - ferry - cable car - gondola - funicular + Tram + Subway + Rail + Bus + Ferry + Cable Car + Gondola + Funicular @@ -1255,6 +1255,8 @@ Reset time Set destination to my location Set origin to my location + Swap origin and destination + Get Directions Opt out of study? If you opt out of the research study, we won\'t receive any more information about your travel behavior to help improve public transit. Cancel