/** * Copyright (c) 2012 Todoroo Inc * * See the file "LICENSE" for the full license governing this code. */ package com.todoroo.astrid.ui; import java.util.HashSet; import java.util.concurrent.atomic.AtomicReference; import android.app.Activity; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; import com.timsu.astrid.R; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.actfm.ActFmLoginActivity; import com.todoroo.astrid.actfm.EditPeopleControlSet; import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; import com.todoroo.astrid.activity.AstridActivity; import com.todoroo.astrid.activity.TaskEditFragment; import com.todoroo.astrid.activity.TaskListActivity; import com.todoroo.astrid.activity.TaskListFragment; import com.todoroo.astrid.activity.TaskListFragment.OnTaskListItemClickedListener; import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.dao.TaskAttachmentDao; import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.data.SyncFlags; import com.todoroo.astrid.data.TagData; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.TaskAttachment; import com.todoroo.astrid.files.FileUtilities; import com.todoroo.astrid.gcal.GCalControlSet; import com.todoroo.astrid.gcal.GCalHelper; import com.todoroo.astrid.repeats.RepeatControlSet; import com.todoroo.astrid.service.AddOnService; import com.todoroo.astrid.service.MetadataService; import com.todoroo.astrid.service.StatisticsConstants; import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.utility.Flags; import com.todoroo.astrid.voice.VoiceRecognizer; /** * Quick Add Bar lets you add tasks. * * @author Tim Su <tim@astrid.com> * */ public class QuickAddBar extends LinearLayout { private ImageButton voiceAddButton; private ImageButton quickAddButton; private EditText quickAddBox; private LinearLayout quickAddControls; private View quickAddControlsContainer; private DeadlineControlSet deadlineControl; private RepeatControlSet repeatControl; private GCalControlSet gcalControl; private EditPeopleControlSet peopleControl; private boolean usePeopleControl = true; private String currentVoiceFile = null; @Autowired AddOnService addOnService; @Autowired ExceptionService exceptionService; @Autowired MetadataService metadataService; @Autowired ActFmPreferenceService actFmPreferenceService; @Autowired private TaskAttachmentDao taskAttachmentDao; private VoiceRecognizer voiceRecognizer; private AstridActivity activity; private TaskListFragment fragment; public QuickAddBar(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public QuickAddBar(Context context, AttributeSet attrs) { super(context, attrs); } public QuickAddBar(Context context) { super(context); } public void initialize(AstridActivity myActivity, TaskListFragment myFragment, final OnTaskListItemClickedListener mListener) { activity = myActivity; fragment = myFragment; DependencyInjectionService.getInstance().inject(this); LayoutInflater.from(activity).inflate(R.layout.quick_add_bar, this); quickAddControls = (LinearLayout) findViewById(R.id.taskListQuickaddControls); quickAddControlsContainer = findViewById(R.id.taskListQuickaddControlsContainer); // set listener for pressing enter in quick-add box quickAddBox = (EditText) findViewById(R.id.quickAddText); quickAddBox.setOnEditorActionListener(new OnEditorActionListener() { /** * When user presses enter, quick-add the task */ @Override public boolean onEditorAction(TextView view, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_NULL && !TextUtils.isEmpty(quickAddBox.getText().toString().trim())) { quickAddTask(quickAddBox.getText().toString(), true); return true; } return false; } }); quickAddBox.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { final boolean controlsVisible = !TextUtils.isEmpty(s) && quickAddBox.hasFocus(); final boolean showControls = Preferences.getBoolean(R.string.p_show_quickadd_controls, true); final boolean plusVisible = !TextUtils.isEmpty(s); final boolean hidePlus = Preferences.getBoolean(R.string.p_hide_plus_button, false); quickAddControlsContainer.postDelayed(new Runnable() { @Override public void run() { quickAddButton.setVisibility((plusVisible || !hidePlus) ? View.VISIBLE : View.GONE); quickAddControlsContainer.setVisibility((showControls && controlsVisible) ? View.VISIBLE : View.GONE); } }, 10); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {/**/} @Override public void afterTextChanged(Editable s) {/**/} }); int fontSize = Preferences.getIntegerFromString(R.string.p_fontSize, 18); quickAddBox.setTextSize(Math.min(fontSize, 22)); quickAddButton = ((ImageButton) findViewById( R.id.quickAddButton)); quickAddButton.setVisibility(Preferences.getBoolean(R.string.p_hide_plus_button, false) ? View.GONE : View.VISIBLE); // set listener for quick add button quickAddButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { Task task = quickAddTask(quickAddBox.getText().toString(), true); if (task != null && task.getValue(Task.TITLE).length() == 0) { mListener.onTaskListItemClicked(task.getId(), true); } } }); // prepare and set listener for voice add button voiceAddButton = (ImageButton) findViewById( R.id.voiceAddButton); voiceAddButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startVoiceRecognition(); } }); // set listener for extended addbutton quickAddButton.setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View v) { Task task = quickAddTask(quickAddBox.getText().toString(), false); if (task == null) return true; mListener.onTaskListItemClicked(task.getId(), true); return true; } }); if (Preferences.getBoolean(R.string.p_voiceInputEnabled, true) && VoiceRecognizer.voiceInputAvailable(activity)) { voiceAddButton.setVisibility(View.VISIBLE); } else { voiceAddButton.setVisibility(View.GONE); } setUpQuickAddControlSets(); } public void setUsePeopleControl(boolean usePeopleControl) { this.usePeopleControl = usePeopleControl; peopleControl.getDisplayView().setVisibility(usePeopleControl ? View.VISIBLE : View.GONE); } private void setUpQuickAddControlSets() { repeatControl = new RepeatControlSet(activity, R.layout.control_set_repeat, R.layout.control_set_repeat_display, R.string.repeat_enabled); gcalControl = new GCalControlSet(activity, R.layout.control_set_gcal, R.layout.control_set_gcal_display, R.string.gcal_TEA_addToCalendar_label); deadlineControl = new DeadlineControlSet(activity, R.layout.control_set_deadline, R.layout.control_set_default_display, null, repeatControl.getDisplayView(), gcalControl.getDisplayView()); deadlineControl.setIsQuickadd(true); peopleControl = new EditPeopleControlSet(activity, fragment, R.layout.control_set_assigned, R.layout.control_set_default_display, R.string.actfm_EPA_assign_label_long, TaskEditFragment.REQUEST_LOG_IN); resetControlSets(); LayoutParams lp = new LinearLayout.LayoutParams( LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT, 1.0f); View peopleDisplay = peopleControl.getDisplayView(); View deadlineDisplay = deadlineControl.getDisplayView(); quickAddControls.addView(peopleDisplay, 0, lp); quickAddControls.addView(deadlineDisplay, 2, lp); TextView tv = (TextView) deadlineDisplay.findViewById(R.id.display_row_edit); tv.setGravity(Gravity.LEFT); tv = (TextView) peopleDisplay.findViewById(R.id.display_row_edit); tv.setGravity(Gravity.LEFT); } private void resetControlSets() { Task empty = new Task(); TagData tagData = fragment.getActiveTagData(); if (tagData != null) { HashSet<String> tagsTransitory = new HashSet<String>(); tagsTransitory.add(tagData.getValue(TagData.NAME)); empty.putTransitory(TaskService.TRANS_TAGS, tagsTransitory); } repeatControl.readFromTask(empty); gcalControl.readFromTask(empty); gcalControl.resetCalendarSelector(); deadlineControl.readFromTask(empty); peopleControl.setUpData(empty, fragment.getActiveTagData()); peopleControl.assignToMe(); peopleControl.setTask(null); } // --- quick add task logic /** * Quick-add a new task * * @param title * @return */ @SuppressWarnings("nls") public Task quickAddTask(String title, boolean selectNewTask) { TagData tagData = fragment.getActiveTagData(); if(tagData != null && (!tagData.containsNonNullValue(TagData.NAME) || tagData.getValue(TagData.NAME).length() == 0)) { DialogUtilities.okDialog(activity, activity.getString(R.string.tag_no_title_error), null); return null; } try { if (title != null) title = title.trim(); boolean assignedToMe = usePeopleControl ? peopleControl.willBeAssignedToMe() : true; if (!assignedToMe && !actFmPreferenceService.isLoggedIn()) { DialogInterface.OnClickListener okListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface d, int which) { activity.startActivity(new Intent(activity, ActFmLoginActivity.class)); } }; DialogInterface.OnClickListener cancelListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface d, int which) { // Reset people control peopleControl.assignToMe(); } }; DialogUtilities.okCancelCustomDialog(activity, activity.getString(R.string.actfm_EPA_login_button), activity.getString(R.string.actfm_EPA_login_to_share), R.string.actfm_EPA_login_button, R.string.actfm_EPA_dont_share_button, android.R.drawable.ic_dialog_alert, okListener, cancelListener); return null; } Task task = new Task(); if (title != null) task.setValue(Task.TITLE, title); // need this for calendar if (repeatControl.isRecurrenceSet()) repeatControl.writeToModel(task); if (deadlineControl.isDeadlineSet()) { task.clearValue(Task.HIDE_UNTIL); deadlineControl.writeToModel(task); TaskDao.createDefaultHideUntil(task); } gcalControl.writeToModel(task); if (!assignedToMe) { peopleControl.setTask(task); peopleControl.saveSharingSettings(null); } TaskService.createWithValues(task, fragment.getFilter().valuesForNewTasks, title); String assignedTo = peopleControl.getAssignedToString(); String assignedEmail = ""; String assignedId = task.getValue(Task.USER_ID); if (Task.userIdIsEmail(task.getValue(Task.USER_ID))) { assignedEmail = task.getValue(Task.USER_ID); } resetControlSets(); addToCalendar(task, title); if(!TextUtils.isEmpty(title)) fragment.showTaskEditHelpPopover(); if (activity instanceof TaskListActivity && !assignedToMe) ((TaskListActivity) activity).taskAssignedTo(assignedTo, assignedEmail, assignedId); TextView quickAdd = (TextView) findViewById(R.id.quickAddText); quickAdd.setText(""); //$NON-NLS-1$ if (selectNewTask) { fragment.loadTaskListContent(true); fragment.selectCustomId(task.getId()); if (task.getTransitory(TaskService.TRANS_QUICK_ADD_MARKUP) != null) { showAlertForMarkupTask((AstridActivity) activity, task, title); } else if (!TextUtils.isEmpty(task.getValue(Task.RECURRENCE))) { showAlertForRepeatingTask((AstridActivity) activity, task); } } if (currentVoiceFile != null) { AtomicReference<String> nameRef = new AtomicReference<String>(); String path = FileUtilities.getNewAudioAttachmentPath(activity, nameRef); voiceRecognizer.convert(path); currentVoiceFile = null; TaskAttachment attachment = TaskAttachment.createNewAttachment(task.getUuid(), path, nameRef.get(), TaskAttachment.FILE_TYPE_AUDIO + "m4a"); taskAttachmentDao.createNew(attachment); } fragment.onTaskCreated(task); StatisticsService.reportEvent(StatisticsConstants.TASK_CREATED_TASKLIST); return task; } catch (Exception e) { exceptionService.displayAndReportError(activity, "quick-add-task", e); return new Task(); } } private static void addToCalendar(Task task, String title) { boolean gcalCreateEventEnabled = Preferences.getStringValue(R.string.gcal_p_default) != null && !Preferences.getStringValue(R.string.gcal_p_default).equals("-1") && task.hasDueDate(); //$NON-NLS-1$ if (!TextUtils.isEmpty(title) && gcalCreateEventEnabled && TextUtils.isEmpty(task.getValue(Task.CALENDAR_URI))) { Uri calendarUri = GCalHelper.createTaskEvent(task, ContextManager.getContext().getContentResolver(), new ContentValues()); task.setValue(Task.CALENDAR_URI, calendarUri.toString()); task.putTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC, true); PluginServices.getTaskService().save(task); } } /** * Static method to quickly add tasks without all the control set nonsense. * Used from the share link activity. * @param title * @return */ public static Task basicQuickAddTask(String title) { if (TextUtils.isEmpty(title)) return null; title = title.trim(); Task task = TaskService.createWithValues(null, title); addToCalendar(task, title); return task; } private static void showAlertForMarkupTask(AstridActivity activity, Task task, String originalText) { DateChangedAlerts.showQuickAddMarkupDialog(activity, task, originalText); } private static void showAlertForRepeatingTask(AstridActivity activity, Task task) { DateChangedAlerts.showRepeatChangedDialog(activity, task); } // --- instance methods public EditText getQuickAddBox() { return quickAddBox; } @Override public void clearFocus() { super.clearFocus(); quickAddBox.clearFocus(); } public void performButtonClick() { quickAddButton.performClick(); } public boolean onActivityResult(int requestCode, int resultCode, Intent data) { // handle the result of voice recognition, put it into the textfield if (voiceRecognizer.handleActivityResult(requestCode, resultCode, data, quickAddBox)) { // if user wants, create the task directly (with defaultvalues) // after saying it Flags.set(Flags.TLA_RESUMED_FROM_VOICE_ADD); if (Preferences.getBoolean(R.string.p_voiceInputCreatesTask, false)) quickAddTask(quickAddBox.getText().toString(), true); // the rest of onActivityResult is totally unrelated to // voicerecognition, so bail out return true; } else if (requestCode == TaskEditFragment.REQUEST_CODE_CONTACT) { if (resultCode == Activity.RESULT_OK) peopleControl.onActivityResult(requestCode, resultCode, data); else peopleControl.assignToMe(); return true; } return false; } public VoiceRecognizer getVoiceRecognizer() { return voiceRecognizer; } public void startVoiceRecognition() { if (VoiceRecognizer.speechRecordingAvailable(activity) && currentVoiceFile == null) { currentVoiceFile = Long.toString(DateUtilities.now()); } voiceRecognizer.startVoiceRecognition(activity, fragment, currentVoiceFile); } public void setupRecognizerApi() { voiceRecognizer = VoiceRecognizer.instantiateVoiceRecognizer(activity, activity, voiceAddButton); } public void destroyRecognizerApi() { voiceRecognizer.destroyRecognizerApi(); } public void hideKeyboard() { InputMethodManager imm = (InputMethodManager) activity.getSystemService( Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(quickAddBox.getWindowToken(), 0); } }