/* * Copyright (C) 2010-2016 Stichting Akvo (Akvo Foundation) * * This file is part of Akvo Flow. * * Akvo Flow is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Akvo Flow is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Akvo Flow. If not, see <http://www.gnu.org/licenses/>. */ package org.akvo.flow.ui.view; import android.content.Context; import android.os.Bundle; import android.text.Editable; import android.text.InputFilter; import android.text.InputType; import android.text.TextUtils; import android.text.TextWatcher; import android.text.method.DigitsKeyListener; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import org.akvo.flow.R; import org.akvo.flow.app.FlowApp; import org.akvo.flow.domain.Question; import org.akvo.flow.domain.QuestionResponse; import org.akvo.flow.domain.ValidationRule; import org.akvo.flow.event.QuestionInteractionEvent; import org.akvo.flow.event.SurveyListener; import org.akvo.flow.exception.ValidationException; import org.akvo.flow.util.ConstantUtil; /** * Question that supports free-text input via the keyboard * * @author Christopher Fagiani */ public class FreetextQuestionView extends QuestionView implements View.OnClickListener { private EditText mEditText, mDoubleEntryText; private boolean mCaptureResponse; public FreetextQuestionView(Context context, Question q, SurveyListener surveyListener) { super(context, q, surveyListener); init(); } private void init() { setQuestionView(R.layout.freetext_question_view); mEditText = (EditText)findViewById(R.id.input_et); mDoubleEntryText = (EditText)findViewById(R.id.double_entry_et); // Show/Hide double entry title & EditText if (isDoubleEntry()) { findViewById(R.id.double_entry_title).setVisibility(VISIBLE); mDoubleEntryText.setVisibility(VISIBLE); mEditText.setInputType(mEditText.getInputType() & ~InputType.TYPE_TEXT_FLAG_MULTI_LINE); } if (isReadOnly()) { mEditText.setFocusable(false); mDoubleEntryText.setFocusable(false); } int maxLength = ValidationRule.DEFAULT_MAX_LENGTH; ValidationRule rule = getQuestion().getValidationRule(); if (rule != null) { // set the maximum length if (rule.getMaxLength() != null) { maxLength = Math.min(rule.getMaxLength(), ValidationRule.DEFAULT_MAX_LENGTH); } // if the type is numeric, add numeric-specific rules if (ConstantUtil.NUMERIC_VALIDATION_TYPE.equalsIgnoreCase(rule.getValidationType())) { DigitsKeyListener MyDigitKeyListener = new DigitsKeyListener( rule.getAllowSigned(), rule.getAllowDecimal()); mEditText.setKeyListener(MyDigitKeyListener); mDoubleEntryText.setKeyListener(MyDigitKeyListener); } } InputFilter[] filters = { new InputFilter.LengthFilter(maxLength) }; mEditText.setFilters(filters); mDoubleEntryText.setFilters(filters); // ResponseListener will handle both onFocusChange and TextChanged mCaptureResponse = true; ResponseListener inputListener = new ResponseListener(mEditText); ResponseListener extraListener = new ResponseListener(mDoubleEntryText); mEditText.addTextChangedListener(inputListener); mEditText.setOnFocusChangeListener(inputListener); mDoubleEntryText.addTextChangedListener(extraListener); mDoubleEntryText.setOnFocusChangeListener(extraListener); Button externalSourceBtn = (Button)findViewById(R.id.external_source_btn); if (mQuestion.useExternalSource()) { externalSourceBtn.setVisibility(VISIBLE); externalSourceBtn.setOnClickListener(this); externalSourceBtn.setEnabled(!mSurveyListener.isReadOnly()); mEditText.setEnabled(false); mEditText.setInputType(mEditText.getInputType() & ~InputType.TYPE_TEXT_FLAG_MULTI_LINE); mDoubleEntryText.setEnabled(false); } else { externalSourceBtn.setVisibility(GONE); } } @Override public void setResponse(QuestionResponse resp) { if (resp != null) { mCaptureResponse = false; mEditText.setText(resp.getValue()); if (isDoubleEntry()) { mDoubleEntryText.setText(resp.getValue()); } mCaptureResponse = true; } super.setResponse(resp); } /** * pulls the data out of the fields and saves it as a response object, * possibly suppressing listeners */ @Override public void captureResponse(boolean suppressListeners) { ValidationRule rule = getQuestion().getValidationRule(); try { if (!TextUtils.isEmpty(mEditText.getText().toString())) { // Do not validate void answers validateText(rule, mEditText); if (isDoubleEntry()) { validateText(rule, mDoubleEntryText); } } } catch (ValidationException e) { // if we failed validation, display a message to the user String error; if (ValidationException.TOO_LARGE.equals(e.getType())) { error = getResources().getString(R.string.toolargeerr) + rule.getMaxValString(); } else if (ValidationException.TOO_SMALL.equals(e.getType())) { error = getResources().getString(R.string.toosmallerr) + rule.getMinValString(); } else { error = getResources().getString(R.string.baddatatypeerr); } setError(error); return;// Die early. Don't store the value. } if (!checkDoubleEntry()) { setError(getResources().getString(R.string.error_answer_match)); return;// Die early. Don't store the value. } setResponse(new QuestionResponse(mEditText.getText().toString(), ConstantUtil.VALUE_RESPONSE_TYPE, getQuestion().getId()), suppressListeners); checkMandatory();// Mandatory question must be answered } private boolean checkDoubleEntry() { if (!isDoubleEntry()) { return true;// No double entry required. Return true; } String text1 = mEditText.getText().toString(); String text2 = mDoubleEntryText.getText().toString(); return text1.equals(text2); } @Override public void rehydrate(QuestionResponse resp) { super.rehydrate(resp); if (resp != null) { mCaptureResponse = false; mEditText.setText(resp.getValue()); if (isDoubleEntry()) { mDoubleEntryText.setText(resp.getValue()); } mCaptureResponse = true; } } @Override public void resetQuestion(boolean fireEvent) { mCaptureResponse = false; mEditText.setText(""); if (isDoubleEntry()) { mDoubleEntryText.setText(""); } mCaptureResponse = true; super.resetQuestion(fireEvent); } @Override public void displayError(String error) { // Display the error within the EditText (instead of question text) mEditText.setError(error); if (isDoubleEntry()) { mDoubleEntryText.setError(error); } } private void validateText(ValidationRule rule, EditText view) throws ValidationException { if (rule != null) { final String text = view.getText().toString(); final String validatedText = rule.performValidation(text); if (!text.equals(validatedText)) { view.setText(validatedText);// This action will trigger captureResponse again } } } @Override public void questionComplete(Bundle data) { if (data != null && data.containsKey(ConstantUtil.CADDISFLY_RESPONSE)) { setResponse(new QuestionResponse(data.getString(ConstantUtil.CADDISFLY_RESPONSE), ConstantUtil.VALUE_RESPONSE_TYPE, getQuestion().getId())); } } @Override public void onClick(View view) { if (view.getId() == R.id.external_source_btn) { Question q = getQuestion(); Bundle data = new Bundle(); data.putString(ConstantUtil.CADDISFLY_QUESTION_ID, q.getId()); data.putString(ConstantUtil.CADDISFLY_QUESTION_TITLE, q.getText()); data.putString(ConstantUtil.CADDISFLY_DATAPOINT_ID, mSurveyListener.getDatapointId()); data.putString(ConstantUtil.CADDISFLY_FORM_ID, mSurveyListener.getFormId()); data.putString(ConstantUtil.CADDISFLY_LANGUAGE, FlowApp.getApp().getAppLanguageCode()); notifyQuestionListeners(QuestionInteractionEvent.EXTERNAL_SOURCE_EVENT, data); } } class ResponseListener implements TextWatcher, OnFocusChangeListener { EditText mView; ResponseListener(EditText view) { mView = view; } boolean capture() { if (!mCaptureResponse) { return false;// Explicitly disabled } if (isDoubleEntry() && mView.getId() == R.id.input_et && TextUtils.isEmpty(mDoubleEntryText.getText().toString())) { // On double entry questions, do not attempt to capture the response if: // 1) the focus lost is happening in the first field and, // 2) second field contains no answer yet return false; } return true; } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { if (capture()) { captureResponse(); } } @Override public void onFocusChange(View view, boolean hasFocus) { if (!hasFocus && capture()) { captureResponse(); } if (!hasFocus) { InputMethodManager imm = (InputMethodManager)getContext().getSystemService( Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } } } }