/* * Copyright (C) 2014-2015 University of South Florida (sjbarbeau@gmail.com) * * 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.report.ui; import org.onebusaway.android.R; import org.onebusaway.android.io.ObaAnalytics; import org.onebusaway.android.io.elements.ObaArrivalInfo; import org.onebusaway.android.io.elements.ObaStop; import org.onebusaway.android.io.elements.ObaTripStatus; import org.onebusaway.android.report.connection.ServiceDescriptionTask; import org.onebusaway.android.report.connection.ServiceRequestTask; import org.onebusaway.android.report.constants.ReportConstants; import org.onebusaway.android.report.ui.model.AttributeValue; import org.onebusaway.android.report.ui.util.IssueLocationHelper; import org.onebusaway.android.report.ui.util.ServiceUtils; import org.onebusaway.android.util.MyTextUtils; import org.onebusaway.android.util.PreferenceUtils; import org.onebusaway.android.util.UIUtils; import android.app.Activity; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.location.Location; import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.provider.MediaStore; import android.support.annotation.Nullable; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.PopupMenu; import android.telephony.TelephonyManager; import android.text.InputType; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.text.style.ForegroundColorSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.RelativeLayout; import android.widget.TextView; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import edu.usf.cutr.open311client.Open311; import edu.usf.cutr.open311client.constants.Open311DataType; import edu.usf.cutr.open311client.models.Open311Attribute; import edu.usf.cutr.open311client.models.Open311AttributePair; import edu.usf.cutr.open311client.models.Open311User; import edu.usf.cutr.open311client.models.Service; import edu.usf.cutr.open311client.models.ServiceDescription; import edu.usf.cutr.open311client.models.ServiceDescriptionRequest; import edu.usf.cutr.open311client.models.ServiceRequest; import edu.usf.cutr.open311client.models.ServiceRequestResponse; import edu.usf.cutr.open311client.utils.Open311Validator; public class Open311ProblemFragment extends BaseReportFragment implements ServiceDescriptionTask.Callback, ServiceRequestTask.Callback { private ImageView mIssueImageView; private String mImagePath; private Open311 mOpen311; private Service mService; private String mAgencyName; // Block ID for the mArrivalInfo private String mBlockId; // Arrival information for trip problem private ObaArrivalInfo mArrivalInfo; // Captured image url private Uri mCapturedImageURI; // Open311 service description result for selected service code private ServiceDescription mServiceDescription; // Store ServiceDescription Task Result if host activity haven't been created private ServiceDescription mServiceDescriptionTaskResult; // Load dynamic open311 fields into info layout private LinearLayout mInfoLayout; private CheckBox mAnonymousReportingCheckBox; private EditText mContactNameView; private EditText mContactLastNameView; private EditText mContactEmailView; private EditText mContactPhoneView; private ProgressDialog mProgressDialog; private boolean mIsProgressDialogShowing = false; private Map<Integer, AttributeValue> mAttributeValueHashMap = new HashMap<>(); private Map<Integer, View> mDynamicAttributeUIMap = new HashMap<>(); // Maps attribute name + id with its key private Map<String, String> mOpen311AttributeKeyNameMap = new HashMap<>(); private ServiceRequestTask mRequestTask; private ReportProblemFragmentCallback mCallback; public static final String TAG = "Open311ProblemFragment"; private static final String ATTRIBUTES = ".attributes"; private static final String IMAGE_PATH = ".image"; private static final String IMAGE_URI = ".imageUri"; private static final String IMAGE_THUMBNAIL = ".imageThumbnail"; private static final String TRIP_INFO = ".tripInfo"; private static final String SHOW_PROGRESS_DIALOG = ".showProgressDialog"; private static final String AGENCY_NAME = ".agencyName"; private static final String BLOCK_ID = ".blockId"; public static void show(AppCompatActivity activity, Integer containerViewId, Open311 open311, Service service, ObaArrivalInfo obaArrivalInfo, String agencyName, String blockId) { FragmentManager fm = activity.getSupportFragmentManager(); Open311ProblemFragment fragment = new Open311ProblemFragment(); fragment.setOpen311(open311); fragment.setService(service); fragment.setArrivalInfo(obaArrivalInfo); fragment.setAgencyName(agencyName); fragment.setBlockId(blockId); try { FragmentTransaction ft = fm.beginTransaction(); ft.replace(containerViewId, fragment, TAG); ft.addToBackStack(null); ft.commit(); } catch (IllegalStateException e) { Log.e(TAG, "Cannot show Open311ProblemFragment after onSaveInstanceState has been called"); } } public static void show(AppCompatActivity activity, Integer containerViewId, Open311 open311, Service service) { Open311ProblemFragment.show(activity, containerViewId, open311, service, null, null, null); } @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.open311_issue, container, false); setRetainInstance(true); setHasOptionsMenu(Boolean.TRUE); return rootView; } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); setupViews(savedInstanceState); setupIconColors(); setUpContactInfoViews(); callServiceDescription(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); List<AttributeValue> attributeValues = createAttributeValues(mServiceDescription); if (attributeValues.size() > 0) { outState.putParcelableArrayList(ATTRIBUTES, (ArrayList<? extends Parcelable>) attributeValues); } if (mImagePath != null) { outState.putParcelable(IMAGE_URI, mCapturedImageURI); outState.putString(IMAGE_PATH, mImagePath); Bitmap bitmap = ((BitmapDrawable) mIssueImageView.getDrawable()).getBitmap(); outState.putParcelable(IMAGE_THUMBNAIL, bitmap); } if (mArrivalInfo != null) { outState.putSerializable(TRIP_INFO, mArrivalInfo); } if (mIsProgressDialogShowing) { // Dismiss the progress dialog when orientation change to prevent leaked window mProgressDialog.dismiss(); } outState.putBoolean(SHOW_PROGRESS_DIALOG, mIsProgressDialogShowing); outState.putString(AGENCY_NAME, mAgencyName); outState.putString(BLOCK_ID, mBlockId); } @Override public void onViewStateRestored(@Nullable Bundle savedInstanceState) { super.onViewStateRestored(savedInstanceState); if (savedInstanceState != null) { mCapturedImageURI = savedInstanceState.getParcelable(IMAGE_URI); mImagePath = savedInstanceState.getString(IMAGE_PATH); mArrivalInfo = (ObaArrivalInfo) savedInstanceState.getSerializable(TRIP_INFO); mAgencyName = savedInstanceState.getString(AGENCY_NAME); mBlockId = savedInstanceState.getString(BLOCK_ID); List<AttributeValue> values = savedInstanceState.getParcelableArrayList(ATTRIBUTES); mAttributeValueHashMap.clear(); if (values != null) { for (AttributeValue v : values) { mAttributeValueHashMap.put(v.getCode(), v); } } mIsProgressDialogShowing = savedInstanceState.getBoolean(SHOW_PROGRESS_DIALOG); if (mIsProgressDialogShowing) { showProgressDialog(true); } } } @Override public void onStart() { super.onStart(); ObaAnalytics.reportFragmentStart(this); } @Override public void onAttach(Context context) { super.onAttach(context); if (mServiceDescriptionTaskResult != null) { // Reload service description task if the activity restored this.onServiceDescriptionTaskCompleted(mServiceDescriptionTaskResult); mServiceDescriptionTaskResult = null; } try { mCallback = (ReportProblemFragmentCallback) context; } catch (ClassCastException e) { throw new ClassCastException("ReportProblemFragmentCallback should be implemented" + " in parent activity"); } } /** * Initialize UI components */ private void setupViews(Bundle bundle) { mIssueImageView = (ImageView) findViewById(R.id.ri_imageView); if (bundle != null && bundle.getParcelable(IMAGE_THUMBNAIL) != null) { mIssueImageView.setImageBitmap((Bitmap) bundle.getParcelable(IMAGE_THUMBNAIL)); } mInfoLayout = (LinearLayout) findViewById(R.id.ri_info_layout); Button addImageButton = (Button) findViewById(R.id.ri_attach_image); final PopupMenu popupMenu = new PopupMenu(getActivity(), addImageButton); popupMenu.inflate(R.menu.report_issue_add_image); addImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { popupMenu.show(); } }); popupMenu.setOnMenuItemClickListener( new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.ri_button_camera: openCamera(); break; case R.id.ri_button_gallery: openGallery(); break; default: break; } return true; } }); mAnonymousReportingCheckBox = (CheckBox) findViewById(R.id.rici_anonymous_checkbox); mAnonymousReportingCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) { disableEnableContactInfoViews(isChecked); } }); // Setup contact info views mContactNameView = ((EditText) findViewById(R.id.rici_name_editText)); mContactLastNameView = ((EditText) findViewById(R.id.rici_lastname_editText)); mContactEmailView = ((EditText) findViewById(R.id.rici_email_editText)); mContactPhoneView = ((EditText) findViewById(R.id.rici_phone_editText)); } private void setupIconColors() { ((ImageView) findViewById(R.id.ri_ic_app_feedback)).setColorFilter( getResources().getColor(R.color.material_gray)); ((ImageView) findViewById(R.id.ri_ic_image_picker)).setColorFilter( getResources().getColor(R.color.material_gray)); ((ImageView) findViewById(R.id.ri_ic_username)).setColorFilter( getResources().getColor(R.color.material_gray)); ((ImageView) findViewById(R.id.ri_ic_customer_service_email)).setColorFilter( getResources().getColor(R.color.material_gray)); ((ImageView) findViewById(R.id.ri_ic_customer_service_phone)).setColorFilter( getResources().getColor(R.color.material_gray)); ((ImageView) findViewById(R.id.ri_ic_anonymous)).setColorFilter( getResources().getColor(R.color.material_gray)); } private void callServiceDescription() { if (mOpen311 == null) { return; } showProgress(Boolean.TRUE); Location location = getIssueLocationHelper().getIssueLocation(); ServiceDescriptionRequest sdr = new ServiceDescriptionRequest(location.getLatitude(), location.getLongitude(), mOpen311.getJurisdiction(), mService.getService_code()); ServiceDescriptionTask sdt = new ServiceDescriptionTask(sdr, mOpen311, Open311ProblemFragment.this); sdt.execute(); } /** * Update ui if service description request is success */ @Override public void onServiceDescriptionTaskCompleted(ServiceDescription serviceDescription) { if (isActivityAttached()) { showProgress(Boolean.FALSE); if (serviceDescription.isSuccess()) { createServiceDescriptionUI(serviceDescription); } else { createToastMessage(getString(R.string.ri_service_description_problem)); // Close open311 fragment ((InfrastructureIssueActivity) getActivity()).removeOpen311ProblemFragment(); } } else { mServiceDescriptionTaskResult = serviceDescription; } } /** * Show the result of the open311 issue submission */ @Override public void onServiceRequestTaskCompleted(ServiceRequestResponse response) { showProgressDialog(false); if (response.isSuccess()) { mCallback.onSendReport(); } else { String message = response.getErrorMessage(); if (TextUtils.isEmpty(message)) { message = getString(R.string.ri_unsuccessful_submit); } createToastMessage(message); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.report_issue_action, menu); super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.report_problem_send) { submitReport(); } return super.onOptionsItemSelected(item); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == ReportConstants.GALLERY_INTENT && resultCode == Activity.RESULT_OK && data != null) { // Get the gallery image info mCapturedImageURI = data.getData(); String[] filePathColumn = {MediaStore.Images.Media.DATA}; Cursor cursor = getActivity().getContentResolver().query(mCapturedImageURI, filePathColumn, null, null, null); if (cursor == null) return; cursor.moveToFirst(); int columnIndex = cursor.getColumnIndex(filePathColumn[0]); mImagePath = cursor.getString(columnIndex); cursor.close(); } // Whether image was from gallery or captured via camera, we need to downscale it for ImageView if ((requestCode == ReportConstants.GALLERY_INTENT || requestCode == ReportConstants.CAPTURE_PICTURE_INTENT) && resultCode == Activity.RESULT_OK) { // Load thumbnail to avoid OutOfMemory issue on Galaxy S5 - see #730 Bitmap thumbnail = null; try { thumbnail = UIUtils.decodeSampledBitmapFromFile(mImagePath, mIssueImageView.getWidth(), mIssueImageView.getHeight()); } catch (IOException e) { e.printStackTrace(); } mIssueImageView.setImageBitmap(thumbnail); } } /** * Prepare submit forms and submit report */ private void submitReport() { // Save the open311 user saveOpen311User(); // Prepare issue description String description = ((EditText) getActivity().findViewById(R.id.ri_editTextDesc)).getText().toString(); final TelephonyManager tm = (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE); Open311User open311User; if (!mAnonymousReportingCheckBox.isChecked()) { open311User = getOpen311UserFromUI(); } else { open311User = getOpen311UserFromStrings(); } IssueLocationHelper issueLocationHelper = getIssueLocationHelper(); ServiceRequest.Builder builder = new ServiceRequest.Builder(); builder.setJurisdiction_id(mOpen311.getJurisdiction()).setService_code(mService.getService_code()). setService_name(mService.getService_name()). setLatitude(issueLocationHelper.getIssueLocation().getLatitude()). setLongitude(issueLocationHelper.getIssueLocation().getLongitude()).setSummary(null). setDescription(description).setEmail(open311User.getEmail()). setFirst_name(open311User.getName()).setLast_name(open311User.getLastName()). setPhone(open311User.getPhone()).setAddress_string(getCurrentAddress()). setDevice_id(tm.getDeviceId()); if (mImagePath != null) { attachImage(builder); } ServiceRequest serviceRequest = builder.createServiceRequest(); List<Open311AttributePair> attributes = createOpen311Attributes(mServiceDescription); serviceRequest.setAttributes(attributes); int errorCode = Open311Validator.validateServiceRequest(serviceRequest, mOpen311.getOpen311Option().getOpen311Type(), mServiceDescription); if (Open311Validator.isValid(errorCode)) { // Append transit service parameters to issue description if (ServiceUtils.isTransitServiceByType(mService.getType())) { description += getTransitIssueParameters(mService); serviceRequest.setDescription(description); } // Start progress showProgressDialog(true); mRequestTask = new ServiceRequestTask(mOpen311, serviceRequest, this); mRequestTask.execute(); ObaAnalytics.reportEventWithCategory(ObaAnalytics.ObaEventCategory.SUBMIT.toString(), getString(R.string.analytics_action_problem), mService.getService_name()); } else { createToastMessage(Open311Validator.getErrorMessageForServiceRequestByErrorCode(errorCode)); } } /** * Attaches the captured image saved in this fragment to the provided builder * * @param builder the builder to attach the image to */ private void attachImage(ServiceRequest.Builder builder) { // Downsample the image file to avoid uploading huge images File smallImageFile = null; try { smallImageFile = UIUtils.createImageFile(getContext(), "-small"); } catch (IOException ex) { // Error occurred while creating the File createToastMessage(getString(R.string.ri_resize_image_problem)); Log.e(TAG, "Couldn't resize image - " + ex); } // Continue only if the File was successfully created if (smallImageFile != null) { // Max SeeClickFix resolution image is "800x600 image center cropped" final int WIDTH = 800, HEIGHT = 800; Bitmap smallImage; FileOutputStream out = null; try { smallImage = UIUtils.decodeSampledBitmapFromFile(mImagePath, WIDTH, HEIGHT); out = new FileOutputStream(smallImageFile); smallImage.compress(Bitmap.CompressFormat.JPEG, 100, out); out.flush(); } catch (IOException e) { e.printStackTrace(); // Just use the full size image smallImageFile = new File(mImagePath); } finally { if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } else { // Just use the full size image smallImageFile = new File(mImagePath); } builder.setMedia(smallImageFile); } /** * Generates stop and trip problem parameters for given open311 service * * @param service open311 service * @return a string containing parameters */ private String getTransitIssueParameters(Service service) { StringBuilder sb = new StringBuilder(); ObaStop obaStop = getIssueLocationHelper().getObaStop(); if (obaStop == null) { return sb.toString(); } sb.append(getString(R.string.ri_append_start)); if (ServiceUtils.isTransitStopServiceByType(service.getType())) { // Append stop service params sb.append(getResources().getString(R.string.ri_append_gtfs_stop_id, obaStop.getId())); sb.append(getResources().getString(R.string.ri_append_stop_name, obaStop.getName())); } else if (ServiceUtils.isTransitTripServiceByType(service.getType())) { if (mArrivalInfo != null) { // Append trip service params DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy"); sb.append(getResources().getString(R.string.ri_append_service_date, dateFormat.format(new Date(mArrivalInfo.getServiceDate())))); if (mAgencyName != null) { sb.append(getResources().getString(R.string.ri_append_agency_name, mAgencyName)); } sb.append(getResources().getString(R.string.ri_append_gtfs_stop_id, obaStop.getId())); sb.append(getResources().getString(R.string.ri_append_stop_name, obaStop.getName())); sb.append(getResources().getString(R.string.ri_append_route_id, mArrivalInfo.getRouteId())); String routeDisplayName = UIUtils.getRouteDisplayName(mArrivalInfo); if (!TextUtils.isEmpty(routeDisplayName)) { sb.append(getResources().getString(R.string.ri_append_route_display_name, routeDisplayName)); } if (mBlockId != null) { sb.append(getResources().getString(R.string.ri_append_block_id, mBlockId)); } sb.append(getResources().getString(R.string.ri_append_trip_id, mArrivalInfo.getTripId())); sb.append(getResources().getString(R.string.ri_append_trip_name, mArrivalInfo.getHeadsign())); sb.append(getResources().getString(R.string.ri_append_predicted, Boolean.valueOf(mArrivalInfo.getPredicted()))); ObaTripStatus tripStatus = mArrivalInfo.getTripStatus(); if (tripStatus != null && mArrivalInfo.getPredicted()) { sb.append(getResources().getString(R.string.ri_append_vehicle_id, mArrivalInfo.getVehicleId())); Location lastKnownLocation = tripStatus.getLastKnownLocation(); if (lastKnownLocation != null) { String locationString = lastKnownLocation.getLatitude() + " " + lastKnownLocation.getLongitude(); sb.append(getResources().getString(R.string.ri_append_vehicle_location, locationString)); } DecimalFormat numberFormat = new DecimalFormat("#.000"); double scheduleDeviation = tripStatus.getScheduleDeviation() / 60.0; if (scheduleDeviation == 0.0) { sb.append(getResources().getString(R.string.ri_append_schedule_deviation, "0")); } else if (scheduleDeviation < 0) { sb.append(getResources().getString(R.string.ri_append_schedule_deviation_early, numberFormat.format(scheduleDeviation * -1.0))); } else if (scheduleDeviation > 0) { sb.append(getResources().getString(R.string.ri_append_schedule_deviation_late, numberFormat.format(scheduleDeviation))); } } dateFormat = new SimpleDateFormat("hh:mm a"); if (mArrivalInfo.getPredicted()) { sb.append(getResources().getString(R.string.ri_append_arrival_time, dateFormat.format(new Date(mArrivalInfo.getPredictedArrivalTime())))); sb.append(getResources().getString(R.string.ri_append_departure_time, dateFormat.format(new Date(mArrivalInfo.getPredictedDepartureTime())))); } else { sb.append(getResources().getString(R.string.ri_append_arrival_time, dateFormat.format(new Date(mArrivalInfo.getScheduledArrivalTime())))); sb.append(getResources().getString(R.string.ri_append_departure_time, dateFormat.format(new Date(mArrivalInfo.getScheduledDepartureTime())))); } } } return sb.toString(); } /** * Creates open311 question and answer attributes to submit a report * Reads from dynamically created UI * * @param serviceDescription contains attribute types * @return List of code value pair of attributes */ private List<Open311AttributePair> createOpen311Attributes(ServiceDescription serviceDescription) { List<Open311AttributePair> attributes = new ArrayList<>(); for (Open311Attribute open311Attribute : serviceDescription.getAttributes()) { if (Boolean.valueOf(open311Attribute.getVariable())) { if (Open311DataType.STRING.equals(open311Attribute.getDatatype()) || Open311DataType.NUMBER.equals(open311Attribute.getDatatype()) || Open311DataType.DATETIME.equals(open311Attribute.getDatatype())) { EditText et = (EditText) mDynamicAttributeUIMap.get(open311Attribute.getCode()); if (et != null) { attributes.add(new Open311AttributePair(open311Attribute.getCode(), et.getText().toString(), open311Attribute.getDatatype())); } } else if (Open311DataType.SINGLEVALUELIST.equals(open311Attribute.getDatatype())) { RadioGroup rg = (RadioGroup) mDynamicAttributeUIMap.get(open311Attribute.getCode()); if (rg != null) { int count = rg.getChildCount(); for (int i = 0; i < count; i++) { RadioButton rb = (RadioButton) rg.getChildAt(i); if (rb.isChecked()) { String attributeKey = mOpen311AttributeKeyNameMap.get( open311Attribute.getCode() + rb.getText().toString()); attributes.add(new Open311AttributePair(open311Attribute.getCode(), attributeKey, open311Attribute.getDatatype())); break; } } } } else if (Open311DataType.MULTIVALUELIST.equals(open311Attribute.getDatatype())) { LinearLayout ll = (LinearLayout) mDynamicAttributeUIMap.get(open311Attribute.getCode()); if (ll != null) { int count = ll.getChildCount(); for (int i = 0; i < count; i++) { CheckBox cb = (CheckBox) ll.getChildAt(i); if (cb.isChecked()) { String attributeKey = mOpen311AttributeKeyNameMap.get( open311Attribute.getCode() + cb.getText().toString()); attributes.add(new Open311AttributePair(open311Attribute.getCode(), attributeKey, open311Attribute.getDatatype())); } } } } } } return attributes; } /** * This method dynamically reads all user inputted the values from the screen and puts into a * list * * @param serviceDescription displayed service description * @return List of attribute values */ private List<AttributeValue> createAttributeValues(ServiceDescription serviceDescription) { List<AttributeValue> values = new ArrayList<>(); if (serviceDescription == null) { return values; } for (Open311Attribute open311Attribute : serviceDescription.getAttributes()) { if (Boolean.valueOf(open311Attribute.getVariable())) { if (Open311DataType.STRING.equals(open311Attribute.getDatatype()) || Open311DataType.NUMBER.equals(open311Attribute.getDatatype()) || Open311DataType.DATETIME.equals(open311Attribute.getDatatype())) { EditText et = (EditText) mDynamicAttributeUIMap.get(open311Attribute.getCode()); if (et != null) { AttributeValue value = new AttributeValue(open311Attribute.getCode()); value.addValue(et.getText().toString()); values.add(value); } } else if (Open311DataType.SINGLEVALUELIST.equals(open311Attribute.getDatatype())) { RadioGroup rg = (RadioGroup) mDynamicAttributeUIMap.get(open311Attribute.getCode()); if (rg != null) { int count = rg.getChildCount(); for (int i = 0; i < count; i++) { RadioButton rb = (RadioButton) rg.getChildAt(i); if (rb.isChecked()) { AttributeValue value = new AttributeValue(open311Attribute.getCode()); value.addValue(rb.getText().toString()); values.add(value); break; } } } } else if (Open311DataType.MULTIVALUELIST.equals(open311Attribute.getDatatype())) { LinearLayout ll = (LinearLayout) mDynamicAttributeUIMap.get(open311Attribute.getCode()); if (ll != null) { int count = ll.getChildCount(); AttributeValue value = new AttributeValue(open311Attribute.getCode()); for (int i = 0; i < count; i++) { CheckBox cb = (CheckBox) ll.getChildAt(i); if (cb.isChecked()) { value.addValue(cb.getText().toString()); } } if (value.getValues().size() > 0) values.add(value); } } } } return values; } /** * From https://developer.android.com/training/camera/photobasics.html#TaskPath */ private void openCamera() { Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // Ensure that there's a camera activity to handle the intent if (takePictureIntent.resolveActivity(getActivity().getPackageManager()) != null) { // Create the File where the photo should go File photoFile = null; try { photoFile = UIUtils.createImageFile(getContext(), null); } catch (IOException ex) { // Error occurred while creating the File createToastMessage(getString(R.string.ri_open_camera_problem)); Log.e(TAG, "Couldn't open camera - " + ex); } // Continue only if the File was successfully created if (photoFile != null) { // Save a file: path for use with ACTION_VIEW intents mImagePath = photoFile.getAbsolutePath(); mCapturedImageURI = Uri.fromFile(photoFile); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mCapturedImageURI); startActivityForResult(takePictureIntent, ReportConstants.CAPTURE_PICTURE_INTENT); } } } private void openGallery() { Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(intent, ReportConstants.GALLERY_INTENT); } /** * Dynamically creates Open311 questions from service description * * @param serviceDescription contains Open311 questions */ public void createServiceDescriptionUI(ServiceDescription serviceDescription) { clearInfoField(); this.mServiceDescription = serviceDescription; if (!"".equals(mService.getDescription()) && mService.getDescription() != null) { addDescriptionText(mService.getDescription()); } if (mArrivalInfo != null && ServiceUtils.isTransitTripServiceByType(mService.getType())) { createTripHeadsign(mArrivalInfo.getHeadsign()); } for (Open311Attribute open311Attribute : serviceDescription.getAttributes()) { if (!Boolean.valueOf(open311Attribute.getVariable())) { addDescriptionText(open311Attribute.getDescription()); } else { if (Open311DataType.STRING.equals(open311Attribute.getDatatype()) || Open311DataType.NUMBER.equals(open311Attribute.getDatatype()) || Open311DataType.DATETIME.equals(open311Attribute.getDatatype())) { createEditText(open311Attribute); } else if (Open311DataType.SINGLEVALUELIST.equals(open311Attribute.getDatatype())) { createSingleValueList(open311Attribute); } else if (Open311DataType.MULTIVALUELIST.equals(open311Attribute.getDatatype())) { createMultiValueList(open311Attribute); } } } } /** * Dynamically creates an edit text * * @param open311Attribute contains the open311 attributes */ private void createEditText(Open311Attribute open311Attribute) { LayoutInflater inflater = LayoutInflater.from(getActivity()); RelativeLayout layout = (RelativeLayout) inflater.inflate(R.layout.report_issue_text_item, null, false); ImageView icon = ((ImageView) layout.findViewById(R.id.ri_ic_question_answer)); icon.setColorFilter(getResources().getColor(R.color.material_gray)); Spannable desc = new SpannableString(MyTextUtils.toSentenceCase(open311Attribute.getDescription())); EditText editText = ((EditText) layout.findViewById(R.id.riti_editText)); if (open311Attribute.getRequired()) { Spannable req = new SpannableString("(required)"); req.setSpan(new ForegroundColorSpan(Color.RED), 0, req.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); editText.setHint(TextUtils.concat(desc, " ", req)); } else { editText.setHint(desc); } if (Open311DataType.NUMBER.equals(open311Attribute.getDatatype())) { editText.setInputType(InputType.TYPE_CLASS_NUMBER); } else if (Open311DataType.DATETIME.equals(open311Attribute.getDatatype())) { editText.setInputType(InputType.TYPE_CLASS_DATETIME); } // Restore view state from attribute result hash map AttributeValue av = mAttributeValueHashMap.get(open311Attribute.getCode()); if (av != null) { editText.setText(av.getSingleValue()); } // Dynamically fill stop id if this is a transit service // And if this is a bus stop field if (ServiceUtils.isTransitServiceByType(mService.getType()) && ServiceUtils.isStopIdField(open311Attribute.getDescription())) { icon.setImageDrawable(ContextCompat.getDrawable(getActivity(), R.drawable.ri_flag_triangle)); ObaStop obaStop = getIssueLocationHelper().getObaStop(); if (obaStop != null) { editText.setText(obaStop.getStopCode()); } } mInfoLayout.addView(layout); mDynamicAttributeUIMap.put(open311Attribute.getCode(), editText); } /** * Dynamically creates radio buttons * * @param open311Attribute contains the open311 attributes */ private void createSingleValueList(Open311Attribute open311Attribute) { ArrayList<Object> values = (ArrayList<Object>) open311Attribute.getValues(); if (values != null && values.size() > 0) { LayoutInflater inflater = LayoutInflater.from(getActivity()); RelativeLayout layout = (RelativeLayout) inflater.inflate( R.layout.report_issue_single_value_list_item, null, false); layout.setSaveEnabled(true); ((ImageView) layout.findViewById(R.id.ri_ic_radio)).setColorFilter( getResources().getColor(R.color.material_gray)); Spannable word = new SpannableString(open311Attribute.getDescription()); ((TextView) layout.findViewById(R.id.risvli_textView)).setText(word); if (open311Attribute.getRequired()) { Spannable wordTwo = new SpannableString(" *Required"); wordTwo.setSpan(new ForegroundColorSpan(Color.RED), 0, wordTwo.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); ((TextView) layout.findViewById(R.id.risvli_textView)).append(wordTwo); } RadioGroup rg = (RadioGroup) layout.findViewById(R.id.risvli_radioGroup); rg.setOrientation(RadioGroup.VERTICAL); // Restore view state from attribute result hash map AttributeValue av = mAttributeValueHashMap.get(open311Attribute.getCode()); String entryValue = null; if (av != null) { entryValue = av.getSingleValue(); } for (int i = 0; i < values.size(); i++) { LinkedHashMap<String, String> value = (LinkedHashMap<String, String>) values.get(i); RadioButton rb = new RadioButton(getActivity()); rg.addView(rb); //the RadioButtons are added to the radioGroup instead of the layout String attributeKey = ""; String attributeValue = ""; for (LinkedHashMap.Entry<String, String> entry : value.entrySet()) { if (Open311Attribute.NAME.equals(entry.getKey())) { rb.setText(entry.getValue()); if (entryValue != null && entryValue.equalsIgnoreCase(entry.getValue())) { rb.setChecked(true); } attributeKey = open311Attribute.getCode() + entry.getValue(); } else if (Open311Attribute.KEY.equals(entry.getKey())) { attributeValue = entry.getValue(); } } mOpen311AttributeKeyNameMap.put(attributeKey, attributeValue); } mInfoLayout.addView(layout); mDynamicAttributeUIMap.put(open311Attribute.getCode(), rg); } } /** * Dynamically creates checkboxes * * @param open311Attribute contains the open311 attributes */ private void createMultiValueList(Open311Attribute open311Attribute) { ArrayList<Object> values = (ArrayList<Object>) open311Attribute.getValues(); if (values != null && values.size() > 0) { LayoutInflater inflater = LayoutInflater.from(getActivity()); RelativeLayout layout = (RelativeLayout) inflater.inflate( R.layout.report_issue_multi_value_list_item, null, false); ((ImageView) layout.findViewById(R.id.ri_ic_checkbox)).setColorFilter( getResources().getColor(R.color.material_gray)); Spannable word = new SpannableString(open311Attribute.getDescription()); ((TextView) layout.findViewById(R.id.rimvli_textView)).setText(word); if (open311Attribute.getRequired()) { Spannable wordTwo = new SpannableString(" *Required"); wordTwo.setSpan(new ForegroundColorSpan(Color.RED), 0, wordTwo.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); ((TextView) layout.findViewById(R.id.rimvli_textView)).append(wordTwo); } // Restore view state from attribute result hash map AttributeValue av = mAttributeValueHashMap.get(open311Attribute.getCode()); LinearLayout cg = (LinearLayout) layout.findViewById(R.id.rimvli_checkBoxGroup); for (int i = 0; i < values.size(); i++) { LinkedHashMap<String, String> value = (LinkedHashMap<String, String>) values.get(i); CheckBox cb = new CheckBox(getActivity()); cg.addView(cb); String attributeKey = ""; String attributeValue = ""; for (LinkedHashMap.Entry<String, String> entry : value.entrySet()) { if (Open311Attribute.NAME.equals(entry.getKey())) { cb.setText(entry.getValue()); if (av != null && av.getValues().contains(entry.getValue())) { cb.setChecked(true); } attributeKey = open311Attribute.getCode() + entry.getValue(); } else if (Open311Attribute.KEY.equals(entry.getKey())) { attributeValue = entry.getValue(); } } mOpen311AttributeKeyNameMap.put(attributeKey, attributeValue); } mInfoLayout.addView(layout); mDynamicAttributeUIMap.put(open311Attribute.getCode(), cg); } } private void createTripHeadsign(String text) { LayoutInflater inflater = LayoutInflater.from(getActivity()); RelativeLayout layout = (RelativeLayout) inflater.inflate( R.layout.report_issue_description_item, null, false); LinearLayout linear = (LinearLayout) findViewById(R.id.ri_report_stop_problem); TextView tv = ((TextView) layout.findViewById(R.id.riii_textView)); tv.setText(UIUtils.formatDisplayText(text)); tv.setTypeface(null, Typeface.NORMAL); linear.addView(layout, 0); ImageView imageView = (ImageView) layout.findViewById(R.id.ic_action_info); imageView.setImageResource(R.drawable.ic_trip_details); imageView.setColorFilter(getResources().getColor(R.color.material_gray)); } private void clearInfoField() { mInfoLayout.removeAllViewsInLayout(); } private void addDescriptionText(String text) { LayoutInflater inflater = LayoutInflater.from(getActivity()); RelativeLayout layout = (RelativeLayout) inflater.inflate( R.layout.report_issue_description_item, null, false); ((TextView) layout.findViewById(R.id.riii_textView)).setText(text); mInfoLayout.addView(layout); ((ImageView) layout.findViewById(R.id.ic_action_info)).setColorFilter( getResources().getColor(R.color.material_gray)); } /** * Set up USer contact information view */ private void setUpContactInfoViews() { Open311User open311User = getOpen311UserFromSharedPref(); if (open311User.getName() != null) mContactNameView.setText(open311User.getName()); if (open311User.getLastName() != null) mContactLastNameView.setText(open311User.getLastName()); if (open311User.getEmail() != null) mContactEmailView.setText(open311User.getEmail()); if (open311User.getPhone() != null) mContactPhoneView.setText(open311User.getPhone()); } /** * This method disables or enables editing for contact info fields * * @param isDisabled true if you want to disable contact info fields */ private void disableEnableContactInfoViews(boolean isDisabled) { mContactNameView.setEnabled(!isDisabled); mContactLastNameView.setEnabled(!isDisabled); mContactEmailView.setEnabled(!isDisabled); mContactPhoneView.setEnabled(!isDisabled); } /** * Get open311 user from shared preferences * * @return Open311User */ private Open311User getOpen311UserFromSharedPref() { return new Open311User(PreferenceUtils.getString(ReportConstants.PREF_NAME), PreferenceUtils.getString(ReportConstants.PREF_LAST_NAME), PreferenceUtils.getString(ReportConstants.PREF_EMAIL), PreferenceUtils.getString(ReportConstants.PREF_PHONE)); } /** * Get open311 user from fields on the screen * * @return Open311User */ private Open311User getOpen311UserFromUI() { String name = ((EditText) findViewById(R.id.rici_name_editText)).getText().toString(); String lastName = ((EditText) findViewById(R.id.rici_lastname_editText)).getText().toString(); String email = ((EditText) findViewById(R.id.rici_email_editText)).getText().toString(); String phone = ((EditText) findViewById(R.id.rici_phone_editText)).getText().toString(); return new Open311User(name, lastName, email, phone); } /** * Get open311 user from static strings for anonymous reporting * * @return Open311User */ public Open311User getOpen311UserFromStrings() { String name = getString(R.string.ri_static_user_name); String lastName = getString(R.string.ri_static_user_last_name); String email = getString(R.string.ri_static_user_email); String phone = getString(R.string.ri_static_user_phone); return new Open311User(name, lastName, email, phone); } /** * Save open311 user to shared prefs */ private void saveOpen311User() { String name = ((EditText) findViewById(R.id.rici_name_editText)).getText().toString(); String lastName = ((EditText) findViewById(R.id.rici_lastname_editText)).getText().toString(); String email = ((EditText) findViewById(R.id.rici_email_editText)).getText().toString(); String phone = ((EditText) findViewById(R.id.rici_phone_editText)).getText().toString(); PreferenceUtils.saveString(ReportConstants.PREF_NAME, name); PreferenceUtils.saveString(ReportConstants.PREF_LAST_NAME, lastName); PreferenceUtils.saveString(ReportConstants.PREF_EMAIL, email); PreferenceUtils.saveString(ReportConstants.PREF_PHONE, phone); } private IssueLocationHelper getIssueLocationHelper() { return ((InfrastructureIssueActivity) getActivity()).getIssueLocationHelper(); } private String getCurrentAddress() { String address = ((InfrastructureIssueActivity) getActivity()).getCurrentAddress(); if (TextUtils.isEmpty(address)) { return null; } else { return address; } } /** * Show a progress icon on the action bar * * @param visible show or hide the progress icon based */ private void showProgress(Boolean visible) { InfrastructureIssueActivity activity = ((InfrastructureIssueActivity) getActivity()); if (activity != null) { activity.showProgress(visible); } } /** * Show a progress dialog on the screen * * @param visible show or hide the progress icon based */ private void showProgressDialog(boolean visible) { if (visible) { mIsProgressDialogShowing = true; mProgressDialog = new ProgressDialog(getActivity()); mProgressDialog.setMessage(getActivity().getString(R.string.ri_submitting_message)); mProgressDialog.setIndeterminate(true); mProgressDialog.setCancelable(false); mProgressDialog.setCanceledOnTouchOutside(false); mProgressDialog.setButton(ProgressDialog.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { if (mRequestTask != null) { mRequestTask.cancel(true); } mIsProgressDialogShowing = false; mProgressDialog.dismiss(); } }); mProgressDialog.show(); } else if (mProgressDialog != null && !visible && mProgressDialog.isShowing()) { mIsProgressDialogShowing = false; mProgressDialog.dismiss(); } } private boolean isActivityAttached() { return getActivity() != null; } public void setOpen311(Open311 open311) { mOpen311 = open311; } public void setService(Service service) { mService = service; } public void setArrivalInfo(ObaArrivalInfo arrivalInfo) { mArrivalInfo = arrivalInfo; } public void setAgencyName(String agencyName) { this.mAgencyName = agencyName; } public void setBlockId(String blockId) { mBlockId = blockId; } }