/** * Copyright (c) 2012 Todoroo Inc * * See the file "LICENSE" for the full license governing this code. */ package com.todoroo.astrid.activity; import java.io.File; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.ContentValues; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources.Theme; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.GradientDrawable; import android.os.Bundle; import android.os.Handler; import android.speech.RecognizerIntent; import android.support.v4.view.ViewPager; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.View.MeasureSpec; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewParent; import android.webkit.MimeTypeMap; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; import com.actionbarsherlock.app.SherlockFragment; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.timsu.astrid.R; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.sql.Criterion; import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.actfm.ActFmCameraModule; import com.todoroo.astrid.actfm.ActFmCameraModule.CameraResultCallback; import com.todoroo.astrid.actfm.CommentsActivity; import com.todoroo.astrid.actfm.EditPeopleControlSet; import com.todoroo.astrid.actfm.TaskCommentsFragment; import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.dao.TaskAttachmentDao; import com.todoroo.astrid.dao.TaskOutstandingDao; import com.todoroo.astrid.dao.UserDao; import com.todoroo.astrid.dao.WaitingOnMeDao; import com.todoroo.astrid.data.RemoteModel; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.TaskAttachment; import com.todoroo.astrid.data.TaskOutstanding; import com.todoroo.astrid.data.User; import com.todoroo.astrid.data.WaitingOnMe; import com.todoroo.astrid.files.AACRecordingActivity; import com.todoroo.astrid.files.FileExplore; import com.todoroo.astrid.files.FileUtilities; import com.todoroo.astrid.files.FilesControlSet; import com.todoroo.astrid.gcal.GCalControlSet; import com.todoroo.astrid.helper.TaskEditControlSet; import com.todoroo.astrid.notes.EditNoteActivity; import com.todoroo.astrid.opencrx.OpencrxControlSet; import com.todoroo.astrid.opencrx.OpencrxCoreUtils; import com.todoroo.astrid.reminders.Notifications; import com.todoroo.astrid.repeats.RepeatControlSet; import com.todoroo.astrid.service.StatisticsConstants; import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.service.ThemeService; import com.todoroo.astrid.tags.TagsControlSet; import com.todoroo.astrid.timers.TimerActionControlSet; import com.todoroo.astrid.timers.TimerControlSet; import com.todoroo.astrid.timers.TimerPlugin; import com.todoroo.astrid.ui.DateChangedAlerts; import com.todoroo.astrid.ui.DeadlineControlSet; import com.todoroo.astrid.ui.EditNotesControlSet; import com.todoroo.astrid.ui.EditTitleControlSet; import com.todoroo.astrid.ui.HideUntilControlSet; import com.todoroo.astrid.ui.ImportanceControlSet; import com.todoroo.astrid.ui.NestableScrollView; import com.todoroo.astrid.ui.NestableViewPager; import com.todoroo.astrid.ui.PopupControlSet; import com.todoroo.astrid.ui.ReminderControlSet; import com.todoroo.astrid.ui.TaskEditMoreControls; import com.todoroo.astrid.utility.AstridPreferences; import com.todoroo.astrid.utility.Flags; import com.todoroo.astrid.voice.VoiceInputAssistant; import com.todoroo.astrid.voice.VoiceRecognizer; import com.viewpagerindicator.TabPageIndicator; /** * This activity is responsible for creating new tasks and editing existing * ones. It saves a task when it is paused (screen rotated, back button pressed) * as long as the task has a title. * * @author timsu * */ public final class TaskEditFragment extends SherlockFragment implements ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener { public static final String TAG_TASKEDIT_FRAGMENT = "taskedit_fragment"; //$NON-NLS-1$ // --- bundle tokens /** * Task ID */ public static final String TOKEN_ID = "id"; //$NON-NLS-1$ /** * Content Values to set */ public static final String TOKEN_VALUES = "v"; //$NON-NLS-1$ public static final String TOKEN_OPEN_CONTROL = "open_control"; //$NON-NLS-1$ /** * Task in progress (during orientation change) */ private static final String TASK_IN_PROGRESS = "task_in_progress"; //$NON-NLS-1$ /** * Task remote id (during orientation change) */ private static final String TASK_UUID = "task_uuid"; //$NON-NLS-1$ /** * Token for saving a bitmap in the intent before it has been added with a comment */ public static final String TOKEN_PICTURE_IN_PROGRESS = "picture_in_progress"; //$NON-NLS-1$ /** * Tab to start on */ public static final String TOKEN_TAB = "tab"; //$NON-NLS-1$ // --- request codes public static final int REQUEST_LOG_IN = 0; private static final int REQUEST_VOICE_RECOG = 10; public static final int REQUEST_CODE_CONTACT = 20; public static final int REQUEST_CODE_RECORD = 30; public static final int REQUEST_CODE_ATTACH_FILE = 40; public static final int REQUEST_CODE_BEAST_MODE = 50; // --- menu codes private static final int MENU_SAVE_ID = R.string.TEA_menu_save; private static final int MENU_DISCARD_ID = R.string.TEA_menu_discard; private static final int MENU_COMMENTS_REFRESH_ID = R.string.TEA_menu_refresh_comments; private static final int MENU_SHOW_COMMENTS_ID = R.string.TEA_menu_comments; private static final int MENU_ATTACH_ID = R.string.premium_attach_file; private static final int MENU_RECORD_ID = R.string.premium_record_audio; // --- result codes public static final int RESULT_CODE_SAVED = Activity.RESULT_FIRST_USER; public static final int RESULT_CODE_DISCARDED = Activity.RESULT_FIRST_USER + 1; public static final int RESULT_CODE_DELETED = Activity.RESULT_FIRST_USER + 2; public static final String OVERRIDE_FINISH_ANIM = "finishAnim"; //$NON-NLS-1$ public static final String TOKEN_TASK_WAS_ASSIGNED = "task_assigned"; //$NON-NLS-1$ public static final String TOKEN_ASSIGNED_TO_DISPLAY = "task_assigned_to_display"; //$NON-NLS-1$ public static final String TOKEN_ASSIGNED_TO_EMAIL = "task_assigned_to_email"; //$NON-NLS-1$ public static final String TOKEN_ASSIGNED_TO_ID = "task_assigned_to_id"; //$NON-NLS-1$ public static final String TOKEN_TAGS_CHANGED = "tags_changed"; //$NON-NLS-1$ public static final String TOKEN_NEW_REPEATING_TASK = "new_repeating"; //$NON-NLS-1$ // --- services public static final int TAB_VIEW_UPDATES = 0; public static final int TAB_VIEW_MORE = 1; @Autowired private ExceptionService exceptionService; @Autowired private TaskService taskService; @Autowired private TaskOutstandingDao taskOutstandingDao; @Autowired private TaskAttachmentDao taskAttachmentDao; @Autowired private ActFmPreferenceService actFmPreferenceService; @Autowired private WaitingOnMeDao waitingOnMeDao; @Autowired private UserDao userDao; // --- UI components private ImageButton voiceAddNoteButton; private EditPeopleControlSet peopleControlSet = null; private EditNotesControlSet notesControlSet = null; private HideUntilControlSet hideUntilControls = null; private TagsControlSet tagsControlSet = null; private FilesControlSet filesControlSet = null; private TimerActionControlSet timerAction; private EditText title; private TaskEditMoreControls moreControls; private EditNoteActivity editNotes; private NestableViewPager mPager; private TabPageIndicator mIndicator; private HashMap<String, TaskEditControlSet> controlSetMap = new HashMap<String, TaskEditControlSet>(); private final List<TaskEditControlSet> controls = Collections.synchronizedList(new ArrayList<TaskEditControlSet>()); // --- other instance variables /** true if editing started with a new task */ private boolean isNewTask = false; /** task model */ Task model = null; /** whether task should be saved when this activity exits */ private boolean shouldSaveState = true; /** voice assistant for notes-creation */ private VoiceInputAssistant voiceNoteAssistant; private EditText notesEditText; private Dialog whenDialog; private boolean overrideFinishAnim; private String uuid = RemoteModel.NO_UUID; private boolean showEditComments; private int commentIcon = R.drawable.comment_dark_blue; private int tabStyle = 0; private boolean moreSectionHasControls; /* * ====================================================================== * ======================================================= initialization * ====================================================================== */ /** * Container Activity must implement this interface and we ensure that it * does during the onAttach() callback */ public interface OnTaskEditDetailsClickedListener { public void onTaskEditDetailsClicked(int category, int position); } @Override public void onAttach(Activity activity) { super.onAttach(activity); } public TaskEditFragment() { DependencyInjectionService.getInstance().inject(this); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // if we were editing a task already, restore it if (savedInstanceState != null && savedInstanceState.containsKey(TASK_IN_PROGRESS)) { Task task = savedInstanceState.getParcelable(TASK_IN_PROGRESS); if (task != null) { model = task; } if (savedInstanceState.containsKey(TASK_UUID)) { uuid = savedInstanceState.getString(TASK_UUID); } } showEditComments = Preferences.getBoolean(R.string.p_show_task_edit_comments, true); TypedValue tv = new TypedValue(); getActivity().getTheme().resolveAttribute(R.attr.asCommentButtonImg, tv, false); commentIcon = tv.data; getActivity().setResult(Activity.RESULT_OK); } /* * ====================================================================== * ==================================================== UI initialization * ====================================================================== */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); View v = inflater.inflate(R.layout.task_edit_activity, container, false); return v; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // We have a menu item to show in action bar. setHasOptionsMenu(true); AstridActivity activity = (AstridActivity) getActivity(); setUpUIComponents(); adjustInfoPopovers(); Preferences.setBoolean(R.string.p_showed_tap_task_help, true); overrideFinishAnim = false; if (activity != null) { if (activity.getIntent() != null) overrideFinishAnim = activity.getIntent().getBooleanExtra( OVERRIDE_FINISH_ANIM, true); } if (activity instanceof TaskListActivity) ((TaskListActivity) activity).setCommentsButtonVisibility(false); } private void instantiateEditNotes() { if (showEditComments) { long idParam = getActivity().getIntent().getLongExtra(TOKEN_ID, -1L); editNotes = new EditNoteActivity(this, getView(), idParam); editNotes.setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); editNotes.addListener(this); } } private void loadMoreContainer() { View moreTab = (View) getView().findViewById(R.id.more_container); View commentsBar = (View) getView().findViewById(R.id.updatesFooter); long idParam = getActivity().getIntent().getLongExtra(TOKEN_ID, -1L); tabStyle = TaskEditViewPager.TAB_SHOW_ACTIVITY; if (!showEditComments) tabStyle &= ~TaskEditViewPager.TAB_SHOW_ACTIVITY; if (moreSectionHasControls) tabStyle |= TaskEditViewPager.TAB_SHOW_MORE; if (editNotes == null) { instantiateEditNotes(); } else { editNotes.loadViewForTaskID(idParam); } if (timerAction != null && editNotes != null) { timerAction.removeListener(editNotes); timerAction.addListener(editNotes); } if (editNotes != null) editNotes.addListener(this); if (tabStyle == 0) { return; } TaskEditViewPager adapter = new TaskEditViewPager(getActivity(), tabStyle); adapter.parent = this; mPager = (NestableViewPager) getView().findViewById(R.id.pager); mPager.setAdapter(adapter); mIndicator = (TabPageIndicator) getView().findViewById( R.id.indicator); mIndicator.setViewPager(mPager); mIndicator.setOnPageChangeListener(this); if (moreControls.getParent() != null && moreControls.getParent() != mPager) { ((ViewGroup) moreControls.getParent()).removeView(moreControls); } if (showEditComments) commentsBar.setVisibility(View.VISIBLE); moreTab.setVisibility(View.VISIBLE); setCurrentTab(TAB_VIEW_UPDATES); setPagerHeightForPosition(TAB_VIEW_UPDATES); Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { updatesChanged(); } }, 500L); } private void setCurrentTab(int position) { if(mIndicator == null) return; mIndicator.setCurrentItem(position); mPager.setCurrentItem(position); } /** Initialize UI components */ private void setUpUIComponents() { LinearLayout basicControls = (LinearLayout) getView().findViewById( R.id.basic_controls); LinearLayout titleControls = (LinearLayout) getView().findViewById( R.id.title_controls); LinearLayout whenDialogView = (LinearLayout) LayoutInflater.from( getActivity()).inflate(R.layout.task_edit_when_controls, null); moreControls = (TaskEditMoreControls) LayoutInflater.from(getActivity()).inflate( R.layout.task_edit_more_controls, null); constructWhenDialog(whenDialogView); controlSetMap = new HashMap<String, TaskEditControlSet>(); // populate control set EditTitleControlSet editTitle = new EditTitleControlSet(getActivity(), R.layout.control_set_title, R.id.title); title = (EditText) editTitle.getView().findViewById(R.id.title); controls.add(editTitle); titleControls.addView(editTitle.getDisplayView(), 0, new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT, 1.0f)); timerAction = new TimerActionControlSet( getActivity(), getView()); controls.add(timerAction); tagsControlSet = new TagsControlSet(getActivity(), R.layout.control_set_tags, R.layout.control_set_default_display, R.string.TEA_tags_label_long); controls.add(tagsControlSet); controlSetMap.put(getString(R.string.TEA_ctrl_lists_pref), tagsControlSet); // EditPeopleControlSet relies on the "tags" transitory created by the // TagsControlSet, so we put the tags control before the people control peopleControlSet = new EditPeopleControlSet(getActivity(), this, R.layout.control_set_assigned, R.layout.control_set_default_display, R.string.actfm_EPA_assign_label_long, REQUEST_LOG_IN); controls.add(peopleControlSet); controlSetMap.put(getString(R.string.TEA_ctrl_who_pref), peopleControlSet); RepeatControlSet repeatControls = new RepeatControlSet(getActivity(), R.layout.control_set_repeat, R.layout.control_set_repeat_display, R.string.repeat_enabled); GCalControlSet gcalControl = new GCalControlSet(getActivity(), R.layout.control_set_gcal, R.layout.control_set_gcal_display, R.string.gcal_TEA_addToCalendar_label); // The deadline control set contains the repeat controls and the // calendar controls. // NOTE: we add the gcalControl AFTER the // deadline control, because // otherwise the correct date may not be written to the calendar event. // Order matters! DeadlineControlSet deadlineControl = new DeadlineControlSet( getActivity(), R.layout.control_set_deadline, R.layout.control_set_deadline_display, repeatControls, repeatControls.getDisplayView(), gcalControl.getDisplayView()); controlSetMap.put(getString(R.string.TEA_ctrl_when_pref), deadlineControl); controls.add(repeatControls); repeatControls.addListener(editTitle); controls.add(deadlineControl); controls.add(gcalControl); ImportanceControlSet importanceControl = new ImportanceControlSet( getActivity(), R.layout.control_set_importance); controls.add(importanceControl); importanceControl.addListener(editTitle); controlSetMap.put(getString(R.string.TEA_ctrl_importance_pref), importanceControl); notesControlSet = new EditNotesControlSet(getActivity(), R.layout.control_set_notes, R.layout.control_set_notes_display); notesEditText = (EditText) notesControlSet.getView().findViewById( R.id.notes); controls.add(notesControlSet); controlSetMap.put(getString(R.string.TEA_ctrl_notes_pref), notesControlSet); ReminderControlSet reminderControl = new ReminderControlSet( getActivity(), R.layout.control_set_reminders, R.layout.control_set_default_display); controls.add(reminderControl); controlSetMap.put(getString(R.string.TEA_ctrl_reminders_pref), reminderControl); hideUntilControls = new HideUntilControlSet(getActivity(), R.layout.control_set_hide, R.layout.control_set_default_display, R.string.hide_until_prompt); controls.add(hideUntilControls); reminderControl.addViewToBody(hideUntilControls.getDisplayView()); // TODO: Fix the fact that hideUntil doesn't update accordingly with date changes when lazy loaded. Until then, don't lazy load. hideUntilControls.getView(); TimerControlSet timerControl = new TimerControlSet(getActivity(), R.layout.control_set_timers, R.layout.control_set_default_display, R.string.TEA_timer_controls); timerAction.addListener(timerControl); controls.add(timerControl); controlSetMap.put(getString(R.string.TEA_ctrl_timer_pref), timerControl); filesControlSet = new FilesControlSet(getActivity(), R.layout.control_set_files, R.layout.control_set_files_display, R.string.TEA_control_files); controls.add(filesControlSet); controlSetMap.put(getString(R.string.TEA_ctrl_files_pref), filesControlSet); try { if (OpencrxCoreUtils.INSTANCE.isLoggedIn()) { OpencrxControlSet ocrxControl = new OpencrxControlSet( getActivity(), R.layout.control_set_opencrx, R.layout.control_set_opencrx_display, R.string.opencrx_TEA_opencrx_title); controls.add(ocrxControl); basicControls.addView(ocrxControl.getDisplayView()); notesEditText.setHint(R.string.opencrx_TEA_notes); } } catch (Exception e) { Log.e("astrid-error", "loading-control-set", e); //$NON-NLS-1$ //$NON-NLS-2$ } setupBeastModeButton(); getView().findViewById(R.id.delete_task).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { deleteButtonClick(); } }); loadEditPageOrder(false); // Load task data in background new TaskEditBackgroundLoader().start(); } private void setupBeastModeButton() { TextView beastMode = (TextView) getView().findViewById(R.id.edit_beast_mode); TypedValue tv = new TypedValue(); Theme theme = getActivity().getTheme(); theme.resolveAttribute(R.attr.asTextColor, tv, false); int color = tv.data & 0x00ffffff; color = color + 0x7f000000; beastMode.setTextColor(color); beastMode.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(getActivity(), BeastModePreferences.class); intent.setAction(AstridApiConstants.ACTION_SETTINGS); startActivityForResult(intent, REQUEST_CODE_BEAST_MODE); } }); } private void loadEditPageOrder(boolean removeViews) { LinearLayout basicControls = (LinearLayout) getView().findViewById( R.id.basic_controls); if (removeViews) { basicControls.removeAllViews(); moreControls.removeAllViews(); } ArrayList<String> controlOrder = BeastModePreferences.constructOrderedControlList(getActivity()); String[] itemOrder = controlOrder.toArray(new String[controlOrder.size()]); String moreSectionTrigger = getString(R.string.TEA_ctrl_more_pref); String hideAlwaysTrigger = getString(R.string.TEA_ctrl_hide_section_pref); LinearLayout section = basicControls; moreSectionHasControls = false; Class<?> openControl = (Class<?>) getActivity().getIntent().getSerializableExtra(TOKEN_OPEN_CONTROL); for (int i = 0; i < itemOrder.length; i++) { String item = itemOrder[i]; if (item.equals(hideAlwaysTrigger)) { break; // As soon as we hit the hide section, we're done } else if (item.equals(moreSectionTrigger)) { section = moreControls; } else { View controlSet = null; TaskEditControlSet curr = controlSetMap.get(item); if (curr != null) controlSet = (LinearLayout) curr.getDisplayView(); if (controlSet != null) { if ((i + 1 >= itemOrder.length || itemOrder[i + 1].equals(moreSectionTrigger))) { removeTeaSeparator(controlSet); } section.addView(controlSet); if (section == moreControls) moreSectionHasControls = true; } if (curr != null && curr.getClass().equals(openControl) && curr instanceof PopupControlSet) { ((PopupControlSet) curr).getDisplayView().performClick(); } } } getActivity().getIntent().removeExtra(TOKEN_OPEN_CONTROL); } private void removeTeaSeparator(View view) { View teaSeparator = view.findViewById(R.id.TEA_Separator); if (teaSeparator != null) { teaSeparator.setVisibility(View.GONE); } } private void constructWhenDialog(View whenDialogView) { int theme = ThemeService.getEditDialogTheme(); whenDialog = new Dialog(getActivity(), theme); Button dismissDialogButton = (Button) whenDialogView.findViewById(R.id.when_dismiss); dismissDialogButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { DialogUtilities.dismissDialog(getActivity(), whenDialog); } }); DisplayMetrics metrics = new DisplayMetrics(); getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics); whenDialog.setTitle(R.string.TEA_when_dialog_title); whenDialog.addContentView(whenDialogView, new LayoutParams( metrics.widthPixels - (int) (30 * metrics.density), LayoutParams.WRAP_CONTENT)); } /** * Initialize task edit page in the background * * @author Tim Su <tim@todoroo.com> * */ private class TaskEditBackgroundLoader extends Thread { public void onUiThread() { // prepare and set listener for voice-button if (getActivity() != null) { if (VoiceRecognizer.voiceInputAvailable(getActivity())) { voiceAddNoteButton = (ImageButton) notesControlSet.getView().findViewById( R.id.voiceAddNoteButton); voiceAddNoteButton.setVisibility(View.VISIBLE); int prompt = R.string.voice_edit_note_prompt; voiceNoteAssistant = new VoiceInputAssistant(voiceAddNoteButton, REQUEST_VOICE_RECOG); voiceNoteAssistant.setAppend(true); voiceNoteAssistant.setLanguageModel(RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); voiceNoteAssistant.configureMicrophoneButton(TaskEditFragment.this, prompt); } loadMoreContainer(); } } @Override public void run() { AndroidUtilities.sleepDeep(500L); Activity activity = getActivity(); if (activity == null) return; activity.runOnUiThread(new Runnable() { public void run() { onUiThread(); } }); } } /* * ====================================================================== * =============================================== model reading / saving * ====================================================================== */ /** * Loads action item from the given intent * * @param intent */ @SuppressWarnings("nls") protected void loadItem(Intent intent) { if (model != null) { // came from bundle setIsNewTask(model.getValue(Task.TITLE).length() == 0); setupWaitingOnMe(); return; } long idParam = intent.getLongExtra(TOKEN_ID, -1L); if (idParam > -1L) { model = taskService.fetchById(idParam, Task.PROPERTIES); if (model != null && model.containsNonNullValue(Task.UUID)) uuid = model.getValue(Task.UUID); } // not found by id or was never passed an id if (model == null) { String valuesAsString = intent.getStringExtra(TOKEN_VALUES); ContentValues values = null; try { if (valuesAsString != null) values = AndroidUtilities.contentValuesFromSerializedString(valuesAsString); } catch (Exception e) { // oops, can't serialize } model = TaskService.createWithValues(values, null); getActivity().getIntent().putExtra(TOKEN_ID, model.getId()); } if (model.getValue(Task.TITLE).length() == 0) { StatisticsService.reportEvent(StatisticsConstants.CREATE_TASK); // set deletion date until task gets a title model.setValue(Task.DELETION_DATE, DateUtilities.now()); } else { StatisticsService.reportEvent(StatisticsConstants.EDIT_TASK); } setIsNewTask(model.getValue(Task.TITLE).length() == 0); setupWaitingOnMe(); if (model == null) { exceptionService.reportError("task-edit-no-task", new NullPointerException("model")); getActivity().onBackPressed(); return; } // clear notification Notifications.cancelNotifications(model.getId()); } private void setupWaitingOnMe() { if (!isNewTask) { WaitingOnMe wom = waitingOnMeDao.findByTask(model.getUuid()); if (wom != null) { final View waitingOnMe = getView().findViewById(R.id.waiting_on_me); waitingOnMe.setVisibility(View.VISIBLE); int themeColor = getResources().getColor(ThemeService.getTaskEditThemeColor()); TextView dismiss = (TextView) waitingOnMe.findViewById(R.id.wom_dismiss); dismiss.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { WaitingOnMe template = new WaitingOnMe(); template.setValue(WaitingOnMe.DELETED_AT, DateUtilities.now()); waitingOnMeDao.update(WaitingOnMe.TASK_UUID.eq(model.getUuid()), template); waitingOnMe.setVisibility(View.GONE); } }); dismiss.setTextColor(getResources().getColor(R.color.task_edit_deadline_gray)); GradientDrawable gd = new GradientDrawable(); gd.setColor(ThemeService.getDarkVsLight(Color.rgb(0xee, 0xee, 0xee), Color.rgb(0x22, 0x22, 0x22), false)); gd.setCornerRadius(4.0f); dismiss.setBackgroundDrawable(gd); TextView ack = (TextView) waitingOnMe.findViewById(R.id.wom_acknowledge); ack.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { WaitingOnMe template = new WaitingOnMe(); template.setValue(WaitingOnMe.ACKNOWLEDGED, 1); waitingOnMeDao.update(WaitingOnMe.TASK_UUID.eq(model.getUuid()), template); waitingOnMe.setVisibility(View.GONE); } }); ack.setTextColor(themeColor); gd = new GradientDrawable(); gd.setColor(ThemeService.getDarkVsLight(Color.WHITE, Color.rgb(0x22, 0x22, 0x22), false)); gd.setCornerRadius(4.0f); ack.setBackgroundDrawable(gd); TextView womText = (TextView) waitingOnMe.findViewById(R.id.wom_message); womText.setText(getWomText(wom)); womText.setTextColor(themeColor); ImageView womIcon = (ImageView) waitingOnMe.findViewById(R.id.wom_icon); womIcon.setImageResource(ThemeService.getTaskEditDrawable(R.drawable.tea_icn_waiting, R.drawable.tea_icn_waiting_lightblue)); } } } private String getWomText(WaitingOnMe wom) { int resource; String type = wom.getValue(WaitingOnMe.WAIT_TYPE); if (WaitingOnMe.WAIT_TYPE_ASSIGNED.equals(type)) resource = R.string.wom_assigned; else if (WaitingOnMe.WAIT_TYPE_CHANGED_DUE.equals(type)) resource = R.string.wom_changed_due; else if (WaitingOnMe.WAIT_TYPE_COMMENTED.equals(type)) resource = R.string.wom_commented; else if (WaitingOnMe.WAIT_TYPE_MENTIONED.equals(type)) resource = R.string.wom_mentioned; else if (WaitingOnMe.WAIT_TYPE_RAISED_PRI.equals(type)) resource = R.string.wom_raised_pri; else resource = R.string.wom_default; String userString = null; User user = userDao.fetch(wom.getValue(WaitingOnMe.WAITING_USER_ID), User.PROPERTIES); if (user != null) userString = user.getDisplayName(); if (TextUtils.isEmpty(userString)) userString = getString(R.string.ENA_no_user); return getString(resource, userString); } public long getTaskIdInProgress() { if (model != null && model.getId() > 0) return model.getId(); return getActivity().getIntent().getLongExtra(TOKEN_ID, -1); } private void setIsNewTask(boolean isNewTask) { this.isNewTask = isNewTask; Activity activity = getActivity(); if (activity instanceof TaskEditActivity) { ((TaskEditActivity) activity).updateTitle(isNewTask); } } /** Convenience method to populate fields after setting model to null */ public void repopulateFromScratch(Intent intent) { model = null; uuid = RemoteModel.NO_UUID; populateFields(intent); loadMoreContainer(); } /** Populate UI component values from the model */ public void populateFields(Intent intent) { loadItem(intent); synchronized (controls) { if (!taskAttachmentDao.taskHasAttachments(model.getUuid())) { filesControlSet.getDisplayView().setVisibility(View.GONE); } for (TaskEditControlSet controlSet : controls) controlSet.readFromTask(model); } } public void refreshFilesDisplay() { boolean hasAttachments = taskAttachmentDao.taskHasAttachments(model.getUuid()); filesControlSet.getDisplayView().setVisibility(hasAttachments ? View.VISIBLE : View.GONE); filesControlSet.readFromTask(model); } /** Populate UI component values from the model */ private void populateFields() { populateFields(getActivity().getIntent()); } /** Save task model from values in UI components */ public void save(boolean onPause) { if (title == null) return; if (title.getText().length() > 0) model.setValue(Task.DELETION_DATE, 0L); if (title.getText().length() == 0) return; if (isNewTask) { taskOutstandingDao.deleteWhere(Criterion.and(TaskOutstanding.TASK_ID.eq(model.getId()), TaskOutstanding.COLUMN_STRING.eq(Task.TITLE.name), Criterion.or(TaskOutstanding.VALUE_STRING.isNull(), TaskOutstanding.VALUE_STRING.eq("")))); //$NON-NLS-1$ } StringBuilder toast = new StringBuilder(); synchronized (controls) { for (TaskEditControlSet controlSet : controls) { if (controlSet instanceof PopupControlSet) { // Save open control set PopupControlSet popup = (PopupControlSet) controlSet; Dialog d = popup.getDialog(); if (d != null && d.isShowing()) { getActivity().getIntent().putExtra(TOKEN_OPEN_CONTROL, popup.getClass()); } } String toastText = controlSet.writeToModel(model); if (toastText != null) toast.append('\n').append(toastText); } } String processedToast = addDueTimeToToast(toast.toString()); boolean cancelFinish = peopleControlSet != null && !peopleControlSet.saveSharingSettings(processedToast) && !onPause; boolean tagsChanged = Flags.check(Flags.TAGS_CHANGED); model.putTransitory(TaskService.TRANS_EDIT_SAVE, true); taskService.save(model); if (!onPause && !cancelFinish) { boolean taskEditActivity = (getActivity() instanceof TaskEditActivity); boolean isAssignedToMe = peopleControlSet.isAssignedToMe(); boolean showRepeatAlert = model.getTransitory(TaskService.TRANS_REPEAT_CHANGED) != null && !TextUtils.isEmpty(model.getValue(Task.RECURRENCE)); String assignedTo = peopleControlSet.getAssignedToString(); String assignedEmail = ""; //$NON-NLS-1$ String assignedId = Task.USER_ID_IGNORE; if (Task.userIdIsEmail(model.getValue(Task.USER_ID))) { assignedEmail = model.getValue(Task.USER_ID); } if (taskEditActivity) { Intent data = new Intent(); if (!isAssignedToMe) { data.putExtra(TOKEN_TASK_WAS_ASSIGNED, true); data.putExtra(TOKEN_ASSIGNED_TO_DISPLAY, assignedTo); if (!TextUtils.isEmpty(assignedEmail)) data.putExtra(TOKEN_ASSIGNED_TO_EMAIL, assignedEmail); if (Task.isRealUserId(assignedId)); data.putExtra(TOKEN_ASSIGNED_TO_ID, assignedId); } if (showRepeatAlert) { data.putExtra(TOKEN_NEW_REPEATING_TASK, model); } data.putExtra(TOKEN_TAGS_CHANGED, tagsChanged); getActivity().setResult(Activity.RESULT_OK, data); } else { // Notify task list fragment in multi-column case // since the activity isn't actually finishing TaskListActivity tla = (TaskListActivity) getActivity(); if (!isAssignedToMe) tla.taskAssignedTo(assignedTo, assignedEmail, assignedId); else if (showRepeatAlert) DateChangedAlerts.showRepeatChangedDialog(tla, model); if (tagsChanged) tla.tagsChanged(); tla.refreshTaskList(); } removeExtrasFromIntent(getActivity().getIntent()); shouldSaveState = false; getActivity().onBackPressed(); } } public boolean onKeyDown(int keyCode) { if (keyCode == KeyEvent.KEYCODE_BACK) { if (title.getText().length() == 0 || !peopleControlSet.hasLoadedUI()) discardButtonClick(); else saveButtonClick(); return true; } return false; } @Override public void onDetach() { super.onDetach(); // abandon editing and delete the newly created task if // no title was entered Activity activity = getActivity(); if (overrideFinishAnim) { AndroidUtilities.callOverridePendingTransition(activity, R.anim.slide_right_in, R.anim.slide_right_out); } if (activity instanceof TaskListActivity) { if (title.getText().length() == 0 && isNewTask && model != null && model.isSaved()) { taskService.delete(model); } ((TaskListActivity) activity).setCommentsButtonVisibility(true); } } /** * Helper to remove task edit specific info from activity intent * @param intent */ public static void removeExtrasFromIntent(Intent intent) { if (intent != null) { intent.removeExtra(TaskListActivity.OPEN_TASK); intent.removeExtra(TOKEN_PICTURE_IN_PROGRESS); } } /* * ====================================================================== * ======================================================= event handlers * ====================================================================== */ protected void saveButtonClick() { save(false); } /** * Displays a Toast reporting that the selected task has been saved and, if * it has a due date, that is due in 'x' amount of time, to 1 time-unit of * precision * * @param additionalMessage */ private String addDueTimeToToast(String additionalMessage) { int stringResource; long due = model.getValue(Task.DUE_DATE); String toastMessage; if (due != 0) { stringResource = R.string.TEA_onTaskSave_due; CharSequence formattedDate = DateUtilities.getRelativeDay( getActivity(), due); toastMessage = getString(stringResource, formattedDate); } else { toastMessage = getString(R.string.TEA_onTaskSave_notDue); } return toastMessage + additionalMessage; } protected void discardButtonClick() { shouldSaveState = false; // abandon editing in this case if (title.getText().length() == 0 || TextUtils.isEmpty(model.getValue(Task.TITLE))) { if (isNewTask) { TimerPlugin.updateTimer(getActivity(), model, false); taskService.delete(model); if (getActivity() instanceof TaskListActivity) { TaskListActivity tla = (TaskListActivity) getActivity(); tla.refreshTaskList(); } } } removeExtrasFromIntent(getActivity().getIntent()); getActivity().onBackPressed(); } protected void deleteButtonClick() { new AlertDialog.Builder(getActivity()).setTitle( R.string.DLG_confirm_title).setMessage( R.string.DLG_delete_this_task_question).setIcon( android.R.drawable.ic_dialog_alert).setPositiveButton( android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { TimerPlugin.updateTimer(getActivity(), model, false); taskService.delete(model); shouldSaveState = false; Activity a = getActivity(); if (a instanceof TaskEditActivity) { getActivity().setResult(Activity.RESULT_OK); getActivity().onBackPressed(); } else if (a instanceof TaskListActivity) { discardButtonClick(); TaskListFragment tlf = ((TaskListActivity) a).getTaskListFragment(); if (tlf != null) tlf.refresh(); } } }).setNegativeButton(android.R.string.cancel, null).show(); } private void startAttachFile() { ArrayList<String> options = new ArrayList<String>(); options.add(getString(R.string.file_add_picture)); options.add(getString(R.string.file_add_sdcard)); ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_spinner_dropdown_item, options.toArray(new String[options.size()])); DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface d, int which) { if(which == 0) { ActFmCameraModule.showPictureLauncher(TaskEditFragment.this, null); } else if (which == 1) { Intent attachFile = new Intent(getActivity(), FileExplore.class); startActivityForResult(attachFile, REQUEST_CODE_ATTACH_FILE); } } }; // show a menu of available options new AlertDialog.Builder(getActivity()) .setAdapter(adapter, listener) .show().setOwnerActivity(getActivity()); } private void startRecordingAudio() { Intent recordAudio = new Intent(getActivity(), AACRecordingActivity.class); recordAudio.putExtra(AACRecordingActivity.EXTRA_TEMP_FILE, getActivity().getFilesDir() + File.separator + "audio.aac"); //$NON-NLS-1$ startActivityForResult(recordAudio, REQUEST_CODE_RECORD); } private void attachFile(String file) { File src = new File(file); if (!src.exists()) { Toast.makeText(getActivity(), R.string.file_err_copy, Toast.LENGTH_LONG).show(); return; } File dst = new File(FileUtilities.getAttachmentsDirectory(getActivity()) + File.separator + src.getName()); try { AndroidUtilities.copyFile(src, dst); } catch (Exception e) { Toast.makeText(getActivity(), R.string.file_err_copy, Toast.LENGTH_LONG).show(); return; } String path = dst.getAbsolutePath(); String name = dst.getName(); String extension = AndroidUtilities.getFileExtension(name); String type = TaskAttachment.FILE_TYPE_OTHER; if (!TextUtils.isEmpty(extension)) { MimeTypeMap map = MimeTypeMap.getSingleton(); String guessedType = map.getMimeTypeFromExtension(extension); if (!TextUtils.isEmpty(guessedType)) type = guessedType; } createNewFileAttachment(path, name, type); } @SuppressWarnings("nls") private void attachImage(Bitmap bitmap) { AtomicReference<String> nameRef = new AtomicReference<String>(); String path = FileUtilities.getNewImageAttachmentPath(getActivity(), nameRef); try { FileOutputStream fos = new FileOutputStream(path); bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); fos.flush(); fos.close(); createNewFileAttachment(path, nameRef.get(), TaskAttachment.FILE_TYPE_IMAGE + "png"); } catch (Exception e) { Toast.makeText(getActivity(), R.string.file_err_copy, Toast.LENGTH_LONG).show(); } } private void createNewFileAttachment(String path, String fileName, String fileType) { if (!ActFmPreferenceService.isPremiumUser()) return; TaskAttachment attachment = TaskAttachment.createNewAttachment(model.getUuid(), path, fileName, fileType); taskAttachmentDao.createNew(attachment); filesControlSet.refreshMetadata(); filesControlSet.getDisplayView().setVisibility(View.VISIBLE); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_SAVE_ID: saveButtonClick(); return true; case MENU_DISCARD_ID: discardButtonClick(); return true; case MENU_ATTACH_ID: startAttachFile(); return true; case MENU_RECORD_ID: startRecordingAudio(); return true; case MENU_COMMENTS_REFRESH_ID: { if (editNotes != null) editNotes.refreshData(); return true; } case MENU_SHOW_COMMENTS_ID: { Intent intent = new Intent(getActivity(), CommentsActivity.class); intent.putExtra(TaskCommentsFragment.EXTRA_TASK, model.getId()); startActivity(intent); AndroidUtilities.callOverridePendingTransition(getActivity(), R.anim.slide_left_in, R.anim.slide_left_out); return true; } case android.R.id.home: if (title.getText().length() == 0) discardButtonClick(); else saveButtonClick(); return true; } return super.onOptionsItemSelected(item); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); MenuItem item; if (ActFmPreferenceService.isPremiumUser()) { item = menu.add(Menu.NONE, MENU_ATTACH_ID, 0, R.string.premium_attach_file); item.setIcon(ThemeService.getDrawable(R.drawable.ic_menu_attach)); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); item = menu.add(Menu.NONE, MENU_RECORD_ID, 0, R.string.premium_record_audio); item.setIcon(ThemeService.getDrawable(R.drawable.ic_menu_mic)); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); } boolean useSaveAndCancel = Preferences.getBoolean(R.string.p_save_and_cancel, false); if (useSaveAndCancel || AstridPreferences.useTabletLayout(getActivity())) { if (useSaveAndCancel) { item = menu.add(Menu.NONE, MENU_DISCARD_ID, 0, R.string.TEA_menu_discard); item.setIcon(ThemeService.getDrawable(R.drawable.ic_menu_close)); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); } if (!(getActivity() instanceof TaskEditActivity)) { item = menu.add(Menu.NONE, MENU_SAVE_ID, 0, R.string.TEA_menu_save); item.setIcon(ThemeService.getDrawable(R.drawable.ic_menu_save)); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); } } boolean wouldShowComments = actFmPreferenceService.isLoggedIn() && menu.findItem(MENU_COMMENTS_REFRESH_ID) == null; if(wouldShowComments && showEditComments) { item = menu.add(Menu.NONE, MENU_COMMENTS_REFRESH_ID, Menu.NONE, R.string.ENA_refresh_comments); item.setIcon(R.drawable.icn_menu_refresh_dark); } else if (wouldShowComments && !showEditComments) { item = menu.add(Menu.NONE, MENU_SHOW_COMMENTS_ID, Menu.NONE, R.string.TEA_menu_comments); item.setIcon(commentIcon); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); } } @Override public void onPause() { super.onPause(); StatisticsService.sessionPause(); if (shouldSaveState) save(true); } @Override public void onResume() { super.onResume(); StatisticsService.sessionStart(getActivity()); populateFields(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (editNotes == null) instantiateEditNotes(); if (editNotes != null && editNotes.activityResult(requestCode, resultCode, data)) { return; } else if (requestCode == REQUEST_VOICE_RECOG && resultCode == Activity.RESULT_OK) { // handle the result of voice recognition, put it into the // appropiate textfield voiceNoteAssistant.handleActivityResult(requestCode, resultCode, data, notesEditText); // write the voicenote into the model, or it will be deleted by // onResume.populateFields // (due to the activity-change) notesControlSet.writeToModel(model); } else if (requestCode == REQUEST_CODE_RECORD && resultCode == Activity.RESULT_OK) { String recordedAudioPath = data.getStringExtra(AACRecordingActivity.RESULT_OUTFILE); String recordedAudioName = data.getStringExtra(AACRecordingActivity.RESULT_FILENAME); createNewFileAttachment(recordedAudioPath, recordedAudioName, TaskAttachment.FILE_TYPE_AUDIO + "m4a"); //$NON-NLS-1$ } else if (requestCode == REQUEST_CODE_ATTACH_FILE && resultCode == Activity.RESULT_OK) { attachFile(data.getStringExtra(FileExplore.RESULT_FILE_SELECTED)); } else if (requestCode == REQUEST_CODE_BEAST_MODE) { loadEditPageOrder(true); new TaskEditBackgroundLoader().start(); return; } ActFmCameraModule.activityResult(getActivity(), requestCode, resultCode, data, new CameraResultCallback() { @Override public void handleCameraResult(Bitmap bitmap) { attachImage(bitmap); } }); // respond to sharing logoin peopleControlSet.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // stick our task into the outState outState.putParcelable(TASK_IN_PROGRESS, model); outState.putString(TASK_UUID, uuid.toString()); } @Override public void onStart() { super.onStart(); } @Override public void onStop() { super.onStop(); StatisticsService.sessionStop(getActivity()); } private void adjustInfoPopovers() { Preferences.setBoolean(R.string.p_showed_tap_task_help, true); if (!Preferences.isSet(getString(R.string.p_showed_lists_help))) Preferences.setBoolean(R.string.p_showed_lists_help, false); } /* * ====================================================================== * ========================================== UI component helper classes * ====================================================================== */ public int getTabForPosition(int position) { int tab = TaskEditViewPager.getPageForPosition(position, tabStyle); switch(tab) { case TaskEditViewPager.TAB_SHOW_ACTIVITY: return TAB_VIEW_UPDATES; case TaskEditViewPager.TAB_SHOW_MORE: return TAB_VIEW_MORE; } // error experienced return TAB_VIEW_MORE; } /** * Returns the correct view for TaskEditViewPager * * @param position * in the horizontal scroll view */ public View getPageView(int position) { switch(getTabForPosition(position)) { case TAB_VIEW_MORE: return moreControls; case TAB_VIEW_UPDATES: return editNotes; } return null; } private void setPagerHeightForPosition(int position) { int height = 0; View view = null; switch(getTabForPosition(position)) { case TAB_VIEW_MORE: view = moreControls; break; case TAB_VIEW_UPDATES: view = editNotes; break; } if (view == null || mPager == null) return; int desiredWidth = MeasureSpec.makeMeasureSpec(view.getWidth(), MeasureSpec.AT_MOST); view.measure(desiredWidth, MeasureSpec.UNSPECIFIED); height = Math.max(view.getMeasuredHeight(), height);; LayoutParams pagerParams = mPager.getLayoutParams(); if (height > 0 && height != pagerParams.height) { pagerParams.height = height; mPager.setLayoutParams(pagerParams); } } public static void setViewHeightBasedOnChildren(LinearLayout view) { int totalHeight = 0; int desiredWidth = MeasureSpec.makeMeasureSpec(view.getWidth(), MeasureSpec.AT_MOST); for (int i = 0; i < view.getChildCount(); i++) { View listItem = view.getChildAt(i); listItem.measure(desiredWidth, MeasureSpec.UNSPECIFIED); totalHeight += listItem.getMeasuredHeight(); } ViewGroup.LayoutParams params = view.getLayoutParams(); if(params == null) return; params.height = totalHeight; view.setLayoutParams(params); view.requestLayout(); } // Tab Page listener when page/tab changes @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { return; } @Override public void onPageSelected(final int position) { setPagerHeightForPosition(position); NestableScrollView scrollView = (NestableScrollView)getView().findViewById(R.id.edit_scroll); scrollView.setScrollabelViews(null); } @Override public void onPageScrollStateChanged(int state) { return; } // EditNoteActivity Listener when there are new updates/comments @Override public void updatesChanged() { if (mPager != null && mPager.getCurrentItem() == TAB_VIEW_UPDATES) setPagerHeightForPosition(TAB_VIEW_UPDATES); } // EditNoteActivity Lisener when there are new updates/comments @Override public void commentAdded() { setCurrentTab(TAB_VIEW_UPDATES); setPagerHeightForPosition(TAB_VIEW_UPDATES); scrollToView(editNotes); } // Scroll to view in edit task public void scrollToView(View v) { View child = v; ScrollView scrollView = (ScrollView) getView().findViewById(R.id.edit_scroll); int top = v.getTop(); while (!child.equals(scrollView) ) { top += child.getTop(); ViewParent parentView = child.getParent(); if (parentView != null && View.class.isInstance(parentView)) { child = (View) parentView; } else { break; } } scrollView.smoothScrollTo(0, top); } }