From d4920d61187883d4098edae07b87049cde728945 Mon Sep 17 00:00:00 2001 From: Tim Su Date: Thu, 21 Jan 2010 23:29:12 -0800 Subject: [PATCH] Merge with 1/16 updates to koxx3 provider branch. ------------------------------------------------------------ Use --include-merges or -n0 to see merged revisions. --- AndroidManifest.xml | 2 +- src/com/timsu/astrid/activities/TaskEdit.java | 1536 +++++++++-------- .../timsu/astrid/data/tag/TagController.java | 30 +- .../timsu/astrid/provider/TasksProvider.java | 231 +-- 4 files changed, 873 insertions(+), 926 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index c0ffe703c..7e9c98234 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="124" android:versionName="2.10.3"> diff --git a/src/com/timsu/astrid/activities/TaskEdit.java b/src/com/timsu/astrid/activities/TaskEdit.java index 92d20b002..5703f7912 100644 --- a/src/com/timsu/astrid/activities/TaskEdit.java +++ b/src/com/timsu/astrid/activities/TaskEdit.java @@ -88,8 +88,8 @@ import com.timsu.astrid.widget.TimeDurationControlSet.TimeDurationType; /** * 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. + * ones. It saves a task when it is paused (screen rotated, back button + * pressed) as long as the task has a title. * * @author timsu * @@ -182,757 +182,783 @@ public class TaskEdit extends TaskModificationTabbedActivity { // disable name input box until user requests it AstridUtilities.suppressVirtualKeyboard(name); - } - - @Override - protected TaskModelForEdit getModel(TaskIdentifier identifier) { - if (identifier != null) - return controller.fetchTaskForEdit(this, identifier); - else - return controller.createNewTaskForEdit(); - } - - /* - * ====================================================================== - * =============================================== model reading / saving - * ====================================================================== - */ - - /** Populate UI component values from the model */ - private void populateFields() { - Resources r = getResources(); - - // set UI components based on model variables - if (model.getCursor() != null) - startManagingCursor(model.getCursor()); - if (model.getTaskIdentifier() == null) { - FlurryAgent.onEvent("create-task"); - Bundle extras = getIntent().getExtras(); - if (extras != null && extras.containsKey(START_CHAR_TOKEN)) - name.setText("" + extras.getChar(START_CHAR_TOKEN)); - } else { - FlurryAgent.onEvent("edit-task"); - name.setText(model.getName()); - } - - if (model.getName().length() > 0) - setTitle(new StringBuilder().append(r.getString(R.string.taskEdit_titlePrefix)).append(" ").append( - model.getName())); - estimatedDuration.setTimeDuration(model.getEstimatedSeconds()); - elapsedDuration.setTimeDuration(model.getElapsedSeconds()); - importance.setImportance(model.getImportance()); - definiteDueDate.setDate(model.getDefiniteDueDate()); - preferredDueDate.setDate(model.getPreferredDueDate()); - hiddenUntil.setDate(model.getHiddenUntil()); - notification.setTimeDuration(model.getNotificationIntervalSeconds()); - flags.setValue(model.getNotificationFlags()); - notes.setText(model.getNotes()); - if (model.getTaskIdentifier() == null) { - Integer reminder = Preferences.getDefaultReminder(this); - if (reminder != null) - notification.setTimeDuration(24 * 3600 * reminder); - } - if (model.getCalendarUri() != null) - addToCalendar.setText(r.getString(R.string.showCalendar_label)); - - // tags (only configure if not already set) - if (tagsContainer.getChildCount() == 0) { - tags = tagController.getAllTags(); - if (model.getTaskIdentifier() != null) { - taskTags = tagController.getTaskTags(model.getTaskIdentifier()); - if (taskTags.size() > 0) { - Map tagsMap = new HashMap(); - for (TagModelForView tag : tags) - tagsMap.put(tag.getTagIdentifier(), tag); - for (TagIdentifier id : taskTags) { - if (!tagsMap.containsKey(id)) - continue; - - TagModelForView tag = tagsMap.get(id); - addTag(tag.getName()); - } - } - } else { - taskTags = new LinkedList(); - - Bundle extras = getIntent().getExtras(); - if (extras != null && extras.containsKey(TAG_NAME_TOKEN)) { - addTag(extras.getString(TAG_NAME_TOKEN)); - } - } - addTag(""); - } - - // alerts - if (model.getTaskIdentifier() != null) { - List alerts = alertController.getTaskAlerts(model.getTaskIdentifier()); - for (Date alert : alerts) { - addAlert(alert); - } - } - - // repeats - RepeatInfo repeatInfo = model.getRepeat(); - if (repeatInfo != null) { - setRepeatValue(repeatInfo.getValue()); - repeatInterval.setSelection(repeatInfo.getInterval().ordinal()); - } else - setRepeatValue(0); - - } - - /** Save task model from values in UI components */ - private void save() { - // don't save if user accidentally created a new task - if (name.getText().length() == 0) - return; - - // tell the task list to update itself - TaskListSubActivity.shouldRefreshTaskList = true; - - model.setName(name.getText().toString()); - model.setEstimatedSeconds(estimatedDuration.getTimeDurationInSeconds()); - model.setElapsedSeconds(elapsedDuration.getTimeDurationInSeconds()); - model.setImportance(importance.getImportance()); - model.setDefiniteDueDate(definiteDueDate.getDate()); - model.setPreferredDueDate(preferredDueDate.getDate()); - model.setHiddenUntil(hiddenUntil.getDate()); - model.setNotificationFlags(flags.getValue()); - model.setNotes(notes.getText().toString()); - model.setNotificationIntervalSeconds(notification.getTimeDurationInSeconds()); - model.setRepeat(getRepeatValue()); - - try { - // write out to database - controller.saveTask(model, false); - saveTags(); - saveAlerts(); - Notifications.updateAlarm(this, controller, alertController, model); - - Date dueDate = model.getPreferredDueDate(); - if (dueDate == null) { - dueDate = model.getDefiniteDueDate(); - } - if (dueDate != null && model.getProgressPercentage() != TaskModelForEdit.COMPLETE_PERCENTAGE) { - showSaveToast(dueDate); - } else { - showSaveToast(); - } - - } catch (Exception e) { - Log.e("astrid", "Error saving", e); - } - } - - /** - * Displays a Toast reporting that the selected task has been saved and is - * due in 'x' amount of time, to 2 time-units of precision (e.g. Days + - * Hours). - * - * @param dueDate - * the Date when the task is due - */ - private void showSaveToast(Date dueDate) { - int stringResource; - - int timeInSeconds = (int) ((dueDate.getTime() - System.currentTimeMillis()) / 1000L); - - if (timeInSeconds < 0) { - timeInSeconds *= -1; // DateUtilities.getDurationString() requires - // positive integer - stringResource = R.string.taskEdit_onTaskSave_Overdue; - } else { - stringResource = R.string.taskEdit_onTaskSave_Due; - } - String formattedDate = DateUtilities.getDurationString(getResources(), timeInSeconds, 2); - Toast.makeText(this, getResources().getString(stringResource, formattedDate), Toast.LENGTH_SHORT).show(); - } - - /** - * Displays a Toast reporting that the selected task has been saved. Use - * this version when no due Date has been set. - */ - private void showSaveToast() { - Toast.makeText(this, R.string.taskEdit_onTaskSave_notDue, Toast.LENGTH_SHORT).show(); - } - - /** Save task tags. Must be called after task already has an ID */ - private void saveTags() { - Set tagsToDelete; - Set tagsToAdd; - - HashSet tagNames = new HashSet(); - for (int i = 0; i < tagsContainer.getChildCount(); i++) { - TextView tagName = (TextView) tagsContainer.getChildAt(i).findViewById(R.id.text1); - if (tagName.getText().length() == 0) - continue; - tagNames.add(tagName.getText().toString()); - } - - // map names to tag identifiers, creating them if necessary - HashSet tagIds = new HashSet(); - HashMap tagsByName = new HashMap(); - for (TagModelForView tag : tags) - tagsByName.put(tag.getName(), tag.getTagIdentifier()); - for (String tagName : tagNames) { - if (tagsByName.containsKey(tagName)) - tagIds.add(tagsByName.get(tagName)); - else { - TagIdentifier newTagId = tagController.createTag(tagName); - tagIds.add(newTagId); - } - } - - // intersect tags to figure out which we need to add / remove - tagsToDelete = new HashSet(taskTags); - tagsToDelete.removeAll(tagIds); - tagsToAdd = tagIds; - tagsToAdd.removeAll(taskTags); - - // perform the database updates - for (TagIdentifier tagId : tagsToDelete) - tagController.removeTag(model.getTaskIdentifier(), tagId); - for (TagIdentifier tagId : tagsToAdd) - tagController.addTag(model.getTaskIdentifier(), tagId); - - if (tagsToDelete.size() > 0 || tagsToAdd.size() > 0) - SyncDataController.taskUpdated(this, model); - } - - /** Helper method to save alerts for this task */ - private void saveAlerts() { - alertController.removeAlerts(model.getTaskIdentifier()); - - for (int i = 0; i < alertsContainer.getChildCount(); i++) { - DateControlSet dateControlSet = (DateControlSet) alertsContainer.getChildAt(i).getTag(); - Date date = dateControlSet.getDate(); - alertController.addAlert(model.getTaskIdentifier(), date); - } - } - - /* - * ====================================================================== - * ==================================================== UI initialization - * ====================================================================== - */ - - /** Initialize UI components */ - private void setUpUIComponents() { - Resources r = getResources(); - setTitle(new StringBuilder().append(r.getString(R.string.taskEdit_titleGeneric))); - - // populate instance variables - name = (EditText) findViewById(R.id.name); - importance = new ImportanceControlSet(R.id.importance_container); - tagsContainer = (LinearLayout) findViewById(R.id.tags_container); - estimatedDuration = new TimeDurationControlSet(this, R.id.estimatedDuration, 0, R.string.hour_minutes_dialog, - TimeDurationType.HOURS_MINUTES); - elapsedDuration = new TimeDurationControlSet(this, R.id.elapsedDuration, 0, R.string.hour_minutes_dialog, - TimeDurationType.HOURS_MINUTES); - notification = new TimeDurationControlSet(this, R.id.notification, R.string.notification_prefix, - R.string.notification_dialog, TimeDurationType.DAYS_HOURS); - definiteDueDate = new DateWithNullControlSet(this, R.id.definiteDueDate_notnull, R.id.definiteDueDate_date, - R.id.definiteDueDate_time); - preferredDueDate = new DateWithNullControlSet(this, R.id.preferredDueDate_notnull, R.id.preferredDueDate_date, - R.id.preferredDueDate_time); - hiddenUntil = new DateWithNullControlSet(this, R.id.hiddenUntil_notnull, R.id.hiddenUntil_date, - R.id.hiddenUntil_time); - notes = (EditText) findViewById(R.id.notes); - flags = new NotifyFlagControlSet(R.id.flag_before, R.id.flag_during, R.id.flag_after, R.id.flag_nonstop); - alertsContainer = (LinearLayout) findViewById(R.id.alert_container); - repeatInterval = (Spinner) findViewById(R.id.repeat_interval); - repeatValue = (Button) findViewById(R.id.repeat_value); - addToCalendar = (CheckBox) findViewById(R.id.add_to_calendar); - - // individual ui component initialization - ArrayAdapter repeatAdapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, - RepeatInterval.getLabels(getResources())); - repeatAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - repeatInterval.setAdapter(repeatAdapter); - } - - /** Set up button listeners */ - private void setUpListeners() { - Button saveButtonGeneral = (Button) findViewById(R.id.save_general); - saveButtonGeneral.setOnClickListener(mSaveListener); - - Button saveButtonDates = (Button) findViewById(R.id.save_dates); - saveButtonDates.setOnClickListener(mSaveListener); - - Button saveButtonNotify = (Button) findViewById(R.id.save_notify); - saveButtonNotify.setOnClickListener(mSaveListener); - - Button discardButtonGeneral = (Button) findViewById(R.id.discard_general); - discardButtonGeneral.setOnClickListener(mDiscardListener); - - Button discardButtonDates = (Button) findViewById(R.id.discard_dates); - discardButtonDates.setOnClickListener(mDiscardListener); - - Button discardButtonNotify = (Button) findViewById(R.id.discard_notify); - discardButtonNotify.setOnClickListener(mDiscardListener); - - Button deleteButtonGeneral = (Button) findViewById(R.id.delete_general); - Button deleteButtonDates = (Button) findViewById(R.id.delete_dates); - Button deleteButtonNotify = (Button) findViewById(R.id.delete_notify); - if (model.getTaskIdentifier() == null) { - deleteButtonGeneral.setVisibility(View.GONE); - deleteButtonDates.setVisibility(View.GONE); - deleteButtonNotify.setVisibility(View.GONE); - } else { - deleteButtonGeneral.setOnClickListener(mDeleteListener); - deleteButtonDates.setOnClickListener(mDeleteListener); - deleteButtonNotify.setOnClickListener(mDeleteListener); - } - - Button addAlertButton = (Button) findViewById(R.id.addAlert); - addAlertButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View view) { - addAlert(null); - } - }); - - repeatValue.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - repeatValueClick(); - } - }); - } - - /** Set up the repeat value button */ - private void setRepeatValue(int value) { - if (value == 0) - repeatValue.setText(R.string.repeat_value_unset); - else - repeatValue.setText(Integer.toString(value)); - repeatValue.setTag(value); - } - - private RepeatInfo getRepeatValue() { - if (repeatValue.getTag().equals(0)) - return null; - return new RepeatInfo(RepeatInterval.values()[repeatInterval.getSelectedItemPosition()], (Integer) repeatValue - .getTag()); - } - - /** Adds an alert to the alert field */ - private boolean addAlert(Date alert) { - if (alertsContainer.getChildCount() >= MAX_ALERTS) - return false; - - LayoutInflater inflater = getLayoutInflater(); - final View alertItem = inflater.inflate(R.layout.edit_alert_item, null); - alertsContainer.addView(alertItem); - - DateControlSet dcs = new DateControlSet(this, (Button) alertItem.findViewById(R.id.date), (Button) alertItem - .findViewById(R.id.time)); - dcs.setDate(alert); - alertItem.setTag(dcs); - - ImageButton reminderRemoveButton; - reminderRemoveButton = (ImageButton) alertItem.findViewById(R.id.button1); - reminderRemoveButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - alertsContainer.removeView(alertItem); - } - }); - - return true; - } - - /** Adds a tag to the tag field */ - private boolean addTag(String tagName) { - if (tagsContainer.getChildCount() >= MAX_TAGS) { - return false; - } - - LayoutInflater inflater = getLayoutInflater(); - final View tagItem = inflater.inflate(R.layout.edit_tag_item, null); - tagsContainer.addView(tagItem); - - AutoCompleteTextView textView = (AutoCompleteTextView) tagItem.findViewById(R.id.text1); - textView.setText(tagName); - ArrayAdapter tagsAdapter = new ArrayAdapter(this, - android.R.layout.simple_dropdown_item_1line, tags); - textView.setAdapter(tagsAdapter); - textView.addTextChangedListener(new TextWatcher() { - public void onTextChanged(CharSequence s, int start, int before, int count) { - if (start == 0 && tagsContainer.getChildAt(tagsContainer.getChildCount() - 1) == tagItem) { - addTag(""); - } - } - - public void afterTextChanged(Editable s) { - // - } - - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // - } - }); - - ImageButton reminderRemoveButton; - reminderRemoveButton = (ImageButton) tagItem.findViewById(R.id.button1); - reminderRemoveButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - tagsContainer.removeView(tagItem); - } - }); - - return true; - } - - /* - * ====================================================================== - * ======================================================= event handlers - * ====================================================================== - */ - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - - if (hasFocus && TaskList.shouldCloseInstance) { // user wants to quit - finish(); - } - } - - private void saveButtonClick() { - setResult(RESULT_OK); - finish(); - } - - private void discardButtonClick() { - shouldSaveState = false; - setResult(Constants.RESULT_DISCARD); - finish(); - } - - private void deleteButtonClick() { - new AlertDialog.Builder(this).setTitle(R.string.delete_title).setMessage(R.string.delete_this_task_title) - .setIcon(android.R.drawable.ic_dialog_alert).setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - // tell the task list to update itself - TaskListSubActivity.shouldRefreshTaskList = true; - - controller.deleteTask(model.getTaskIdentifier()); - shouldSaveState = false; - setResult(Constants.RESULT_GO_HOME); - finish(); - } - }).setNegativeButton(android.R.string.cancel, null).show(); - } - - private void repeatValueClick() { - final int tagValue = (Integer) repeatValue.getTag(); - if (tagValue > 0) - repeatHelpShown = true; - - final Runnable openDialogRunnable = new Runnable() { - public void run() { - repeatHelpShown = true; - - int dialogValue = tagValue; - if (dialogValue == 0) - dialogValue = 1; - - new NumberPickerDialog(TaskEdit.this, new OnNumberPickedListener() { - public void onNumberPicked(NumberPicker view, int number) { - setRepeatValue(number); - } - }, getResources().getString(R.string.repeat_picker_title), dialogValue, 1, 0, 31).show(); - } - }; - - if (repeatHelpShown || !Preferences.shouldShowRepeatHelp(this)) { - openDialogRunnable.run(); - return; - } - - new AlertDialog.Builder(this).setTitle(R.string.repeat_help_dialog_title).setMessage( - R.string.repeat_help_dialog).setIcon(android.R.drawable.ic_dialog_info).setPositiveButton( - android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - openDialogRunnable.run(); - } - }).setNeutralButton(R.string.repeat_help_hide, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - Preferences.setShowRepeatHelp(TaskEdit.this, false); - openDialogRunnable.run(); - } - }).show(); - } - - @Override - public boolean onMenuItemSelected(int featureId, MenuItem item) { - switch (item.getItemId()) { - case SAVE_ID: - saveButtonClick(); - return true; - case DISCARD_ID: - discardButtonClick(); - return true; - case DELETE_ID: - deleteButtonClick(); - return true; - } - - return super.onMenuItemSelected(featureId, item); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - MenuItem item; - - item = menu.add(Menu.NONE, SAVE_ID, 0, R.string.save_label); - item.setIcon(android.R.drawable.ic_menu_save); - item.setAlphabeticShortcut('s'); - - item = menu.add(Menu.NONE, DISCARD_ID, 0, R.string.discard_label); - item.setIcon(android.R.drawable.ic_menu_close_clear_cancel); - item.setAlphabeticShortcut('c'); - - if (model.getTaskIdentifier() != null) { - item = menu.add(Menu.NONE, DELETE_ID, 0, R.string.delete_label); - item.setIcon(android.R.drawable.ic_menu_delete); - item.setAlphabeticShortcut('d'); - } - - return true; - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - // save the tag name token for when we rotate the screen - Bundle extras = getIntent().getExtras(); - if (extras != null && extras.containsKey(TAG_NAME_TOKEN)) - outState.putString(TAG_NAME_TOKEN, extras.getString(TAG_NAME_TOKEN)); - } - - /** - * Take the values from the model and set the calendar start and end times - * based on these. Sets keys 'dtstart' and 'dtend'. - * - * @param preferred - * preferred due date or null - * @param definite - * definite due date or null - * @param estimatedSeconds - * estimated duration or null - * @param values - */ - public static void createCalendarStartEndTimes(Date preferred, Date definite, Integer estimatedSeconds, - ContentValues values) { - FlurryAgent.onEvent("create-calendar-event"); - - Long deadlineDate = null; - if (preferred != null && preferred.after(new Date())) - deadlineDate = preferred.getTime(); - else if (definite != null) - deadlineDate = definite.getTime(); - else - deadlineDate = System.currentTimeMillis() + 24 * 3600 * 1000L; - - int estimatedTime = DEFAULT_CAL_TIME; - if (estimatedSeconds != null && estimatedSeconds > 0) { - estimatedTime = estimatedSeconds; - } - values.put("dtstart", deadlineDate - estimatedTime * 1000L); - values.put("dtend", deadlineDate); - } - - @Override - protected void onPause() { - // create calendar event - if (addToCalendar.isChecked() && model.getCalendarUri() == null) { - Uri uri = Uri.parse("content://calendar/events"); - ContentResolver cr = getContentResolver(); - - ContentValues values = new ContentValues(); - values.put("title", name.getText().toString()); - values.put("calendar_id", 1); - values.put("description", notes.getText().toString()); - values.put("hasAlarm", 0); - values.put("transparency", 0); - values.put("visibility", 0); - - createCalendarStartEndTimes(model.getPreferredDueDate(), model.getDefiniteDueDate(), model - .getEstimatedSeconds(), values); - - Uri result = cr.insert(uri, values); - if (result != null) - model.setCalendarUri(result.toString()); - else - Log.e("astrid", "Error creating calendar event!"); - } - - if (shouldSaveState) - save(); - - if (addToCalendar.isChecked() && model.getCalendarUri() != null) { - Uri result = Uri.parse(model.getCalendarUri()); - Intent intent = new Intent(Intent.ACTION_EDIT, result); - - ContentValues values = new ContentValues(); - createCalendarStartEndTimes(model.getPreferredDueDate(), model.getDefiniteDueDate(), model - .getEstimatedSeconds(), values); - - intent.putExtra("beginTime", values.getAsLong("dtstart")); - intent.putExtra("endTime", values.getAsLong("dtend")); - - startActivity(intent); - } - - super.onPause(); - } - - @Override - protected void onResume() { - super.onResume(); - populateFields(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - tagController.close(); - alertController.close(); - } - - /* - * ====================================================================== - * ========================================== UI component helper classes - * ====================================================================== - */ - - /** Control set dealing with notification flags */ - public class NotifyFlagControlSet { - private CheckBox before, during, after, nonstop; - - public NotifyFlagControlSet(int beforeId, int duringId, int afterId, int nonstopId) { - before = (CheckBox) findViewById(beforeId); - during = (CheckBox) findViewById(duringId); - after = (CheckBox) findViewById(afterId); - nonstop = (CheckBox) findViewById(nonstopId); - } - - public void setValue(int flags) { - before.setChecked((flags & TaskModelForEdit.NOTIFY_BEFORE_DEADLINE) > 0); - during.setChecked((flags & TaskModelForEdit.NOTIFY_AT_DEADLINE) > 0); - after.setChecked((flags & TaskModelForEdit.NOTIFY_AFTER_DEADLINE) > 0); - nonstop.setChecked((flags & TaskModelForEdit.NOTIFY_NONSTOP) > 0); - } - - public int getValue() { - int value = 0; - if (before.isChecked()) - value |= TaskModelForEdit.NOTIFY_BEFORE_DEADLINE; - if (during.isChecked()) - value |= TaskModelForEdit.NOTIFY_AT_DEADLINE; - if (after.isChecked()) - value |= TaskModelForEdit.NOTIFY_AFTER_DEADLINE; - if (nonstop.isChecked()) - value |= TaskModelForEdit.NOTIFY_NONSTOP; - return value; - } - } - - /** Control set dealing with importance */ - public class ImportanceControlSet { - private List buttons = new LinkedList(); - - public ImportanceControlSet(int containerId) { - LinearLayout layout = (LinearLayout) findViewById(containerId); - Resources r = getResources(); - - for (Importance i : Importance.values()) { - final ToggleButton button = new ToggleButton(TaskEdit.this); - button.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, - LayoutParams.WRAP_CONTENT, 1)); - button.setTextColor(r.getColor(i.getColorResource())); - button.setTextOff(r.getString(i.getLabelResource())); - button.setTextOn(r.getString(i.getLabelResource())); - button.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - setImportance((Importance) button.getTag()); - } - }); - button.setTag(i); - - buttons.add(button); - layout.addView(button); - } - } - - public void setImportance(Importance i) { - for (CompoundButton b : buttons) { - if (b.getTag() == i) { - b.setTextSize(24); - b.setChecked(true); - } else { - b.setTextSize(16); - b.setChecked(false); - } - } - } - - public Importance getImportance() { - for (CompoundButton b : buttons) - if (b.isChecked()) - return (Importance) b.getTag(); - return Importance.DEFAULT; - } - } - - /** Control set dealing with "blocking on" */ - public class BlockingOnControlSet { - - private CheckBox activatedCheckBox; - private Spinner taskBox; - - public BlockingOnControlSet(int checkBoxId, int taskBoxId) { - activatedCheckBox = (CheckBox) findViewById(checkBoxId); - taskBox = (Spinner) findViewById(taskBoxId); - - Cursor tasks = controller.getActiveTaskListCursor(); - startManagingCursor(tasks); - SimpleCursorAdapter tasksAdapter = new SimpleCursorAdapter(TaskEdit.this, - android.R.layout.simple_list_item_1, tasks, new String[] { TaskModelForList.getNameField() }, - new int[] { android.R.id.text1 }); - taskBox.setAdapter(tasksAdapter); - - activatedCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - taskBox.setEnabled(isChecked); - } - }); - - } - - public void setBlockingOn(TaskIdentifier value) { - activatedCheckBox.setChecked(value != null); - if (value == null) { - return; - } - - for (int i = 0; i < taskBox.getCount(); i++) - if (taskBox.getItemIdAtPosition(i) == value.getId()) { - taskBox.setSelection(i); - return; - } - - // not found - activatedCheckBox.setChecked(false); - } - - public TaskIdentifier getBlockingOn() { - if (!activatedCheckBox.isChecked()) - return null; - - return new TaskIdentifier(taskBox.getSelectedItemId()); - } - } + } + + @Override + protected TaskModelForEdit getModel(TaskIdentifier identifier) { + if (identifier != null) + return controller.fetchTaskForEdit(this, identifier); + else + return controller.createNewTaskForEdit(); + } + + /* ====================================================================== + * =============================================== model reading / saving + * ====================================================================== */ + + /** Populate UI component values from the model */ + private void populateFields() { + Resources r = getResources(); + + // set UI components based on model variables + if(model.getCursor() != null) + startManagingCursor(model.getCursor()); + if(model.getTaskIdentifier() == null) { + FlurryAgent.onEvent("create-task"); + Bundle extras = getIntent().getExtras(); + if(extras != null && extras.containsKey(START_CHAR_TOKEN)) + name.setText("" + extras.getChar(START_CHAR_TOKEN)); + } else { + FlurryAgent.onEvent("edit-task"); + name.setText(model.getName()); + } + + if(model.getName().length() > 0) + setTitle(new StringBuilder(). + append(r.getString(R.string.taskEdit_titlePrefix)). + append(" "). + append(model.getName())); + estimatedDuration.setTimeDuration(model.getEstimatedSeconds()); + elapsedDuration.setTimeDuration(model.getElapsedSeconds()); + importance.setImportance(model.getImportance()); + definiteDueDate.setDate(model.getDefiniteDueDate()); + preferredDueDate.setDate(model.getPreferredDueDate()); + hiddenUntil.setDate(model.getHiddenUntil()); + notification.setTimeDuration(model.getNotificationIntervalSeconds()); + flags.setValue(model.getNotificationFlags()); + notes.setText(model.getNotes()); + if(model.getTaskIdentifier() == null) { + Integer reminder = Preferences.getDefaultReminder(this); + if(reminder != null) + notification.setTimeDuration(24*3600*reminder); + } + if(model.getCalendarUri() != null) + addToCalendar.setText(r.getString(R.string.showCalendar_label)); + + // tags (only configure if not already set) + if(tagsContainer.getChildCount() == 0) { + tags = tagController.getAllTags(); + if(model.getTaskIdentifier() != null) { + taskTags = tagController.getTaskTags(model.getTaskIdentifier()); + if(taskTags.size() > 0) { + Map tagsMap = + new HashMap(); + for(TagModelForView tag : tags) + tagsMap.put(tag.getTagIdentifier(), tag); + for(TagIdentifier id : taskTags) { + if(!tagsMap.containsKey(id)) + continue; + + TagModelForView tag = tagsMap.get(id); + addTag(tag.getName()); + } + } + } else { + taskTags = new LinkedList(); + + Bundle extras = getIntent().getExtras(); + if(extras != null && extras.containsKey(TAG_NAME_TOKEN)) { + addTag(extras.getString(TAG_NAME_TOKEN)); + } + } + addTag(""); + } + + // alerts + if(model.getTaskIdentifier() != null) { + List alerts = alertController.getTaskAlerts(model.getTaskIdentifier()); + for(Date alert : alerts) { + addAlert(alert); + } + } + + // repeats + RepeatInfo repeatInfo = model.getRepeat(); + if(repeatInfo != null) { + setRepeatValue(repeatInfo.getValue()); + repeatInterval.setSelection(repeatInfo.getInterval().ordinal()); + } else + setRepeatValue(0); + + } + + /** Save task model from values in UI components */ + private void save() { + // don't save if user accidentally created a new task + if(name.getText().length() == 0) + return; + + // tell the task list to update itself + TaskListSubActivity.shouldRefreshTaskList = true; + + model.setName(name.getText().toString()); + model.setEstimatedSeconds(estimatedDuration.getTimeDurationInSeconds()); + model.setElapsedSeconds(elapsedDuration.getTimeDurationInSeconds()); + model.setImportance(importance.getImportance()); + model.setDefiniteDueDate(definiteDueDate.getDate()); + model.setPreferredDueDate(preferredDueDate.getDate()); + model.setHiddenUntil(hiddenUntil.getDate()); + model.setNotificationFlags(flags.getValue()); + model.setNotes(notes.getText().toString()); + model.setNotificationIntervalSeconds(notification.getTimeDurationInSeconds()); + model.setRepeat(getRepeatValue()); + + try { + // write out to database + controller.saveTask(model, false); + saveTags(); + saveAlerts(); + Notifications.updateAlarm(this, controller, alertController, model); + + Date dueDate = model.getPreferredDueDate(); + if (dueDate == null) { + dueDate = model.getDefiniteDueDate(); + } + if (dueDate != null && model.getProgressPercentage() != TaskModelForEdit.COMPLETE_PERCENTAGE) { + showSaveToast(dueDate); + } else { + showSaveToast(); + } + + } catch (Exception e) { + Log.e("astrid", "Error saving", e); + } + } + + /** + * Displays a Toast reporting that the selected task has been saved and is + * due in 'x' amount of time, to 2 time-units of precision (e.g. Days + Hours). + * @param dueDate the Date when the task is due + */ + private void showSaveToast(Date dueDate) { + int stringResource; + + int timeInSeconds = (int)((dueDate.getTime() - System.currentTimeMillis())/1000L); + + if (timeInSeconds < 0) { + timeInSeconds *= -1; // DateUtilities.getDurationString() requires positive integer + stringResource = R.string.taskEdit_onTaskSave_Overdue; + } else { + stringResource = R.string.taskEdit_onTaskSave_Due; + } + String formattedDate = DateUtilities.getDurationString(getResources(), timeInSeconds, 2); + Toast.makeText(this, + getResources().getString(stringResource, formattedDate), + Toast.LENGTH_SHORT).show(); + } + + /** + * Displays a Toast reporting that the selected task has been saved. + * Use this version when no due Date has been set. + */ + private void showSaveToast() { + Toast.makeText(this, R.string.taskEdit_onTaskSave_notDue, Toast.LENGTH_SHORT).show(); + } + + /** Save task tags. Must be called after task already has an ID */ + private void saveTags() { + Set tagsToDelete; + Set tagsToAdd; + + HashSet tagNames = new HashSet(); + for(int i = 0; i < tagsContainer.getChildCount(); i++) { + TextView tagName = (TextView)tagsContainer.getChildAt(i).findViewById(R.id.text1); + if(tagName.getText().length() == 0) + continue; + tagNames.add(tagName.getText().toString()); + } + + // map names to tag identifiers, creating them if necessary + HashSet tagIds = new HashSet(); + HashMap tagsByName = new HashMap(); + for(TagModelForView tag : tags) + tagsByName.put(tag.getName(), tag.getTagIdentifier()); + for(String tagName : tagNames) { + if(tagsByName.containsKey(tagName)) + tagIds.add(tagsByName.get(tagName)); + else { + TagIdentifier newTagId = tagController.createTag(tagName); + tagIds.add(newTagId); + } + } + + // intersect tags to figure out which we need to add / remove + tagsToDelete = new HashSet(taskTags); + tagsToDelete.removeAll(tagIds); + tagsToAdd = tagIds; + tagsToAdd.removeAll(taskTags); + + // perform the database updates + for(TagIdentifier tagId : tagsToDelete) + tagController.removeTag(model.getTaskIdentifier(), tagId); + for(TagIdentifier tagId : tagsToAdd) + tagController.addTag(model.getTaskIdentifier(), tagId); + + if(tagsToDelete.size() > 0 || tagsToAdd.size() > 0) + SyncDataController.taskUpdated(this, model); + } + + /** Helper method to save alerts for this task */ + private void saveAlerts() { + alertController.removeAlerts(model.getTaskIdentifier()); + + for(int i = 0; i < alertsContainer.getChildCount(); i++) { + DateControlSet dateControlSet = (DateControlSet)alertsContainer. + getChildAt(i).getTag(); + Date date = dateControlSet.getDate(); + alertController.addAlert(model.getTaskIdentifier(), date); + } + } + + /* ====================================================================== + * ==================================================== UI initialization + * ====================================================================== */ + + /** Initialize UI components */ + private void setUpUIComponents() { + Resources r = getResources(); + setTitle(new StringBuilder() + .append(r.getString(R.string.taskEdit_titleGeneric))); + + // populate instance variables + name = (EditText)findViewById(R.id.name); + importance = new ImportanceControlSet(R.id.importance_container); + tagsContainer = (LinearLayout)findViewById(R.id.tags_container); + estimatedDuration = new TimeDurationControlSet(this, + R.id.estimatedDuration, 0, R.string.hour_minutes_dialog, + TimeDurationType.HOURS_MINUTES); + elapsedDuration = new TimeDurationControlSet(this, R.id.elapsedDuration, + 0, R.string.hour_minutes_dialog, + TimeDurationType.HOURS_MINUTES); + notification = new TimeDurationControlSet(this, R.id.notification, + R.string.notification_prefix, R.string.notification_dialog, + TimeDurationType.DAYS_HOURS); + definiteDueDate = new DateWithNullControlSet(this, R.id.definiteDueDate_notnull, + R.id.definiteDueDate_date, R.id.definiteDueDate_time); + preferredDueDate = new DateWithNullControlSet(this, R.id.preferredDueDate_notnull, + R.id.preferredDueDate_date, R.id.preferredDueDate_time); + hiddenUntil = new DateWithNullControlSet(this, R.id.hiddenUntil_notnull, + R.id.hiddenUntil_date, R.id.hiddenUntil_time); + notes = (EditText)findViewById(R.id.notes); + flags = new NotifyFlagControlSet(R.id.flag_before, + R.id.flag_during, R.id.flag_after, R.id.flag_nonstop); + alertsContainer = (LinearLayout)findViewById(R.id.alert_container); + repeatInterval = (Spinner)findViewById(R.id.repeat_interval); + repeatValue = (Button)findViewById(R.id.repeat_value); + addToCalendar = (CheckBox)findViewById(R.id.add_to_calendar); + + // individual ui component initialization + ArrayAdapter repeatAdapter = new ArrayAdapter( + this, android.R.layout.simple_spinner_item, + RepeatInterval.getLabels(getResources())); + repeatAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + repeatInterval.setAdapter(repeatAdapter); + } + + /** Set up button listeners */ + private void setUpListeners() { + Button saveButtonGeneral = (Button) findViewById(R.id.save_general); + saveButtonGeneral.setOnClickListener(mSaveListener); + + Button saveButtonDates = (Button) findViewById(R.id.save_dates); + saveButtonDates.setOnClickListener(mSaveListener); + + Button saveButtonNotify = (Button) findViewById(R.id.save_notify); + saveButtonNotify.setOnClickListener(mSaveListener); + + Button discardButtonGeneral = (Button) findViewById(R.id.discard_general); + discardButtonGeneral.setOnClickListener(mDiscardListener); + + Button discardButtonDates = (Button) findViewById(R.id.discard_dates); + discardButtonDates.setOnClickListener(mDiscardListener); + + Button discardButtonNotify = (Button) findViewById(R.id.discard_notify); + discardButtonNotify.setOnClickListener(mDiscardListener); + + Button deleteButtonGeneral = (Button) findViewById(R.id.delete_general); + Button deleteButtonDates = (Button) findViewById(R.id.delete_dates); + Button deleteButtonNotify = (Button) findViewById(R.id.delete_notify); + if(model.getTaskIdentifier() == null) { + deleteButtonGeneral.setVisibility(View.GONE); + deleteButtonDates.setVisibility(View.GONE); + deleteButtonNotify.setVisibility(View.GONE); + } else { + deleteButtonGeneral.setOnClickListener(mDeleteListener); + deleteButtonDates.setOnClickListener(mDeleteListener); + deleteButtonNotify.setOnClickListener(mDeleteListener); + } + + Button addAlertButton = (Button) findViewById(R.id.addAlert); + addAlertButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + addAlert(null); + } + }); + + repeatValue.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + repeatValueClick(); + } + }); + } + + /** Set up the repeat value button */ + private void setRepeatValue(int value) { + if(value == 0) + repeatValue.setText(R.string.repeat_value_unset); + else + repeatValue.setText(Integer.toString(value)); + repeatValue.setTag(value); + } + + private RepeatInfo getRepeatValue() { + if(repeatValue.getTag().equals(0)) + return null; + return new RepeatInfo(RepeatInterval.values() + [repeatInterval.getSelectedItemPosition()], + (Integer)repeatValue.getTag()); + } + + /** Adds an alert to the alert field */ + private boolean addAlert(Date alert) { + if(alertsContainer.getChildCount() >= MAX_ALERTS) + return false; + + LayoutInflater inflater = getLayoutInflater(); + final View alertItem = inflater.inflate(R.layout.edit_alert_item, null); + alertsContainer.addView(alertItem); + + DateControlSet dcs = new DateControlSet(this, + (Button)alertItem.findViewById(R.id.date), + (Button)alertItem.findViewById(R.id.time)); + dcs.setDate(alert); + alertItem.setTag(dcs); + + ImageButton reminderRemoveButton; + reminderRemoveButton = (ImageButton)alertItem.findViewById(R.id.button1); + reminderRemoveButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + alertsContainer.removeView(alertItem); + } + }); + + return true; + } + + /** Adds a tag to the tag field */ + private boolean addTag(String tagName) { + if (tagsContainer.getChildCount() >= MAX_TAGS) { + return false; + } + + LayoutInflater inflater = getLayoutInflater(); + final View tagItem = inflater.inflate(R.layout.edit_tag_item, null); + tagsContainer.addView(tagItem); + + AutoCompleteTextView textView = (AutoCompleteTextView)tagItem. + findViewById(R.id.text1); + textView.setText(tagName); + ArrayAdapter tagsAdapter = + new ArrayAdapter(this, + android.R.layout.simple_dropdown_item_1line, tags); + textView.setAdapter(tagsAdapter); + textView.addTextChangedListener(new TextWatcher() { + public void onTextChanged(CharSequence s, int start, int before, + int count) { + if(start == 0 && tagsContainer.getChildAt( + tagsContainer.getChildCount()-1) == tagItem) { + addTag(""); + } + } + + public void afterTextChanged(Editable s) { + // + } + + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + // + } + }); + + ImageButton reminderRemoveButton; + reminderRemoveButton = (ImageButton)tagItem.findViewById(R.id.button1); + reminderRemoveButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + tagsContainer.removeView(tagItem); + } + }); + + return true; + } + + /* ====================================================================== + * ======================================================= event handlers + * ====================================================================== */ + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + + if(hasFocus && TaskList.shouldCloseInstance) { // user wants to quit + finish(); + } + } + + private void saveButtonClick() { + setResult(RESULT_OK); + finish(); + } + + private void discardButtonClick() { + shouldSaveState = false; + setResult(Constants.RESULT_DISCARD); + finish(); + } + + private void deleteButtonClick() { + new AlertDialog.Builder(this) + .setTitle(R.string.delete_title) + .setMessage(R.string.delete_this_task_title) + .setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // tell the task list to update itself + TaskListSubActivity.shouldRefreshTaskList = true; + + controller.deleteTask(model.getTaskIdentifier()); + shouldSaveState = false; + setResult(Constants.RESULT_GO_HOME); + finish(); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + private void repeatValueClick() { + final int tagValue = (Integer)repeatValue.getTag(); + if(tagValue > 0) + repeatHelpShown = true; + + final Runnable openDialogRunnable = new Runnable() { + public void run() { + repeatHelpShown = true; + + int dialogValue = tagValue; + if(dialogValue == 0) + dialogValue = 1; + + new NumberPickerDialog(TaskEdit.this, new OnNumberPickedListener() { + public void onNumberPicked(NumberPicker view, int number) { + setRepeatValue(number); + } + }, getResources().getString(R.string.repeat_picker_title), + dialogValue, 1, 0, 31).show(); + } + }; + + if(repeatHelpShown || !Preferences.shouldShowRepeatHelp(this)) { + openDialogRunnable.run(); + return; + } + + new AlertDialog.Builder(this) + .setTitle(R.string.repeat_help_dialog_title) + .setMessage(R.string.repeat_help_dialog) + .setIcon(android.R.drawable.ic_dialog_info) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + openDialogRunnable.run(); + } + }) + .setNeutralButton(R.string.repeat_help_hide, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + Preferences.setShowRepeatHelp(TaskEdit.this, false); + openDialogRunnable.run(); + } + }) + .show(); + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + switch(item.getItemId()) { + case SAVE_ID: + saveButtonClick(); + return true; + case DISCARD_ID: + discardButtonClick(); + return true; + case DELETE_ID: + deleteButtonClick(); + return true; + } + + return super.onMenuItemSelected(featureId, item); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + MenuItem item; + + item = menu.add(Menu.NONE, SAVE_ID, 0, R.string.save_label); + item.setIcon(android.R.drawable.ic_menu_save); + item.setAlphabeticShortcut('s'); + + item = menu.add(Menu.NONE, DISCARD_ID, 0, R.string.discard_label); + item.setIcon(android.R.drawable.ic_menu_close_clear_cancel); + item.setAlphabeticShortcut('c'); + + if (model.getTaskIdentifier() != null) { + item = menu.add(Menu.NONE, DELETE_ID, 0, R.string.delete_label); + item.setIcon(android.R.drawable.ic_menu_delete); + item.setAlphabeticShortcut('d'); + } + + return true; + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + // save the tag name token for when we rotate the screen + Bundle extras = getIntent().getExtras(); + if(extras != null && extras.containsKey(TAG_NAME_TOKEN)) + outState.putString(TAG_NAME_TOKEN, + extras.getString(TAG_NAME_TOKEN)); + } + + /** Take the values from the model and set the calendar start and end times + * based on these. Sets keys 'dtstart' and 'dtend'. + * + * @param preferred preferred due date or null + * @param definite definite due date or null + * @param estimatedSeconds estimated duration or null + * @param values + */ + public static void createCalendarStartEndTimes(Date preferred, Date definite, + Integer estimatedSeconds, ContentValues values) { + FlurryAgent.onEvent("create-calendar-event"); + + Long deadlineDate = null; + if (preferred != null && preferred.after(new Date())) + deadlineDate = preferred.getTime(); + else if (definite != null) + deadlineDate = definite.getTime(); + else + deadlineDate = System.currentTimeMillis() + 24*3600*1000L; + + int estimatedTime = DEFAULT_CAL_TIME; + if(estimatedSeconds != null && estimatedSeconds > 0) { + estimatedTime = estimatedSeconds; + } + values.put("dtstart", deadlineDate - estimatedTime * 1000L); + values.put("dtend", deadlineDate); + } + + @Override + protected void onPause() { + // create calendar event + if(addToCalendar.isChecked() && model.getCalendarUri() == null) { + Uri uri = Uri.parse("content://calendar/events"); + ContentResolver cr = getContentResolver(); + + ContentValues values = new ContentValues(); + values.put("title", name.getText().toString()); + values.put("calendar_id", 1); + values.put("description", notes.getText().toString()); + values.put("hasAlarm", 0); + values.put("transparency", 0); + values.put("visibility", 0); + + createCalendarStartEndTimes(model.getPreferredDueDate(), + model.getDefiniteDueDate(), model.getEstimatedSeconds(), + values); + + Uri result = cr.insert(uri, values); + if(result != null) + model.setCalendarUri(result.toString()); + else + Log.e("astrid", "Error creating calendar event!"); + } + + if(shouldSaveState) + save(); + + if(addToCalendar.isChecked() && model.getCalendarUri() != null) { + Uri result = Uri.parse(model.getCalendarUri()); + Intent intent = new Intent(Intent.ACTION_EDIT, result); + + ContentValues values = new ContentValues(); + createCalendarStartEndTimes(model.getPreferredDueDate(), + model.getDefiniteDueDate(), model.getEstimatedSeconds(), + values); + + intent.putExtra("beginTime", values.getAsLong("dtstart")); + intent.putExtra("endTime", values.getAsLong("dtend")); + + startActivity(intent); + } + + super.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + populateFields(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + tagController.close(); + alertController.close(); + } + + /* ====================================================================== + * ========================================== UI component helper classes + * ====================================================================== */ + + /** Control set dealing with notification flags */ + public class NotifyFlagControlSet { + private CheckBox before, during, after, nonstop; + + public NotifyFlagControlSet(int beforeId, int duringId, + int afterId, int nonstopId) { + before = (CheckBox)findViewById(beforeId); + during = (CheckBox)findViewById(duringId); + after = (CheckBox)findViewById(afterId); + nonstop = (CheckBox)findViewById(nonstopId); + } + + public void setValue(int flags) { + before.setChecked((flags & + TaskModelForEdit.NOTIFY_BEFORE_DEADLINE) > 0); + during.setChecked((flags & + TaskModelForEdit.NOTIFY_AT_DEADLINE) > 0); + after.setChecked((flags & + TaskModelForEdit.NOTIFY_AFTER_DEADLINE) > 0); + nonstop.setChecked((flags & + TaskModelForEdit.NOTIFY_NONSTOP) > 0); + } + + public int getValue() { + int value = 0; + if(before.isChecked()) + value |= TaskModelForEdit.NOTIFY_BEFORE_DEADLINE; + if(during.isChecked()) + value |= TaskModelForEdit.NOTIFY_AT_DEADLINE; + if(after.isChecked()) + value |= TaskModelForEdit.NOTIFY_AFTER_DEADLINE; + if(nonstop.isChecked()) + value |= TaskModelForEdit.NOTIFY_NONSTOP; + return value; + } + } + + /** Control set dealing with importance */ + public class ImportanceControlSet { + private List buttons = new LinkedList(); + + public ImportanceControlSet(int containerId) { + LinearLayout layout = (LinearLayout)findViewById(containerId); + Resources r = getResources(); + + for(Importance i : Importance.values()) { + final ToggleButton button = new ToggleButton(TaskEdit.this); + button.setLayoutParams(new LinearLayout.LayoutParams( + LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT, 1)); + button.setTextColor(r.getColor(i.getColorResource())); + button.setTextOff(r.getString(i.getLabelResource())); + button.setTextOn(r.getString(i.getLabelResource())); + button.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + setImportance((Importance)button.getTag()); + } + }); + button.setTag(i); + + buttons.add(button); + layout.addView(button); + } + } + + public void setImportance(Importance i) { + for(CompoundButton b : buttons) { + if(b.getTag() == i) { + b.setTextSize(24); + b.setChecked(true); + } else { + b.setTextSize(16); + b.setChecked(false); + } + } + } + + public Importance getImportance() { + for(CompoundButton b : buttons) + if(b.isChecked()) + return (Importance)b.getTag(); + return Importance.DEFAULT; + } + } + + /** Control set dealing with "blocking on" */ + public class BlockingOnControlSet { + + private CheckBox activatedCheckBox; + private Spinner taskBox; + + public BlockingOnControlSet(int checkBoxId, int taskBoxId) { + activatedCheckBox = (CheckBox)findViewById(checkBoxId); + taskBox = (Spinner)findViewById(taskBoxId); + + Cursor tasks = controller.getActiveTaskListCursor(); + startManagingCursor(tasks); + SimpleCursorAdapter tasksAdapter = new SimpleCursorAdapter(TaskEdit.this, + android.R.layout.simple_list_item_1, tasks, + new String[] { TaskModelForList.getNameField() }, + new int[] { android.R.id.text1 }); + taskBox.setAdapter(tasksAdapter); + + activatedCheckBox.setOnCheckedChangeListener( + new OnCheckedChangeListener() { + public void onCheckedChanged(CompoundButton buttonView, + boolean isChecked) { + taskBox.setEnabled(isChecked); + } + }); + + } + + public void setBlockingOn(TaskIdentifier value) { + activatedCheckBox.setChecked(value != null); + if(value == null) { + return; + } + + for(int i = 0; i < taskBox.getCount(); i++) + if(taskBox.getItemIdAtPosition(i) == value.getId()) { + taskBox.setSelection(i); + return; + } + + // not found + activatedCheckBox.setChecked(false); + } + + public TaskIdentifier getBlockingOn() { + if(!activatedCheckBox.isChecked()) + return null; + + return new TaskIdentifier(taskBox.getSelectedItemId()); + } + } } diff --git a/src/com/timsu/astrid/data/tag/TagController.java b/src/com/timsu/astrid/data/tag/TagController.java index 59ecd2c2b..d0368655f 100644 --- a/src/com/timsu/astrid/data/tag/TagController.java +++ b/src/com/timsu/astrid/data/tag/TagController.java @@ -34,6 +34,7 @@ import com.timsu.astrid.data.tag.AbstractTagModel.TagModelDatabaseHelper; import com.timsu.astrid.data.tag.TagToTaskMapping.TagToTaskMappingDatabaseHelper; import com.timsu.astrid.data.task.TaskIdentifier; import com.timsu.astrid.data.task.AbstractTaskModel.TaskModelDatabaseHelper; +import com.timsu.astrid.provider.TasksProvider; /** Controller for Tag-related operations */ public class TagController extends AbstractController { @@ -247,8 +248,13 @@ public class TagController extends AbstractController { TagToTaskMapping.TAG + " = " + tagId.idAsString(), null) < 0) return false; - return tagDatabase.delete(TAG_TABLE_NAME, - KEY_ROWID + " = " + tagId.idAsString(), null) > 0; + int res = tagDatabase.delete(TAG_TABLE_NAME, + KEY_ROWID + " = " + tagId.idAsString(), null); + + // notify modification + TasksProvider.notifyDatabaseModification(); + + return res > 0; } // --- single tag to task operations @@ -256,10 +262,16 @@ public class TagController extends AbstractController { /** Remove the given tag from the task */ public boolean removeTag(TaskIdentifier taskId, TagIdentifier tagId) throws SQLException{ - return tagToTaskMapDatabase.delete(TAG_TASK_MAP_NAME, + + int res = tagToTaskMapDatabase.delete(TAG_TASK_MAP_NAME, String.format("%s = ? AND %s = ?", TagToTaskMapping.TAG, TagToTaskMapping.TASK), - new String[] { tagId.idAsString(), taskId.idAsString() }) > 0; + new String[] { tagId.idAsString(), taskId.idAsString() }); + + // notify modification + TasksProvider.notifyDatabaseModification(); + + return res > 0; } /** Add the given tag to the task */ @@ -268,8 +280,14 @@ public class TagController extends AbstractController { ContentValues values = new ContentValues(); values.put(TagToTaskMapping.TAG, tagId.getId()); values.put(TagToTaskMapping.TASK, taskId.getId()); - return tagToTaskMapDatabase.insert(TAG_TASK_MAP_NAME, TagToTaskMapping.TAG, - values) >= 0; + + long res = tagToTaskMapDatabase.insert(TAG_TASK_MAP_NAME, TagToTaskMapping.TAG, + values); + + // notify modification + TasksProvider.notifyDatabaseModification(); + + return res >= 0; } // --- boilerplate diff --git a/src/com/timsu/astrid/provider/TasksProvider.java b/src/com/timsu/astrid/provider/TasksProvider.java index 299c441a8..c57e2edd1 100644 --- a/src/com/timsu/astrid/provider/TasksProvider.java +++ b/src/com/timsu/astrid/provider/TasksProvider.java @@ -1,6 +1,7 @@ package com.timsu.astrid.provider; import java.util.ArrayList; +import java.util.LinkedList; import android.content.ContentProvider; import android.content.ContentValues; @@ -11,7 +12,9 @@ import android.database.MatrixCursor; import android.net.Uri; import android.util.Log; -import com.timsu.astrid.data.task.AbstractTaskModel; +import com.timsu.astrid.data.tag.TagController; +import com.timsu.astrid.data.tag.TagIdentifier; +import com.timsu.astrid.data.tag.TagModelForView; import com.timsu.astrid.data.task.TaskController; import com.timsu.astrid.data.task.TaskModelForProvider; @@ -19,85 +22,48 @@ public class TasksProvider extends ContentProvider { private static final String TAG = "MessageProvider"; + private static final boolean LOGD = false; + + public static final String AUTHORITY = "com.timsu.astrid.tasksprovider"; public static final Uri CONTENT_URI = Uri.parse("content://com.timsu.astrid.tasksprovider"); private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); - private static final int MAX_NUMBEER_OF_TASKS = 20; + private static final int MAX_NUMBEER_OF_TASKS = 30; + private final static String NAME = "name"; private final static String IMPORTANCE_COLOR = "importance_color"; private final static String IDENTIFIER = "identifier"; + private final static String PREFERRED_DUE_DATE = "preferredDueDate"; + private final static String DEFINITE_DUE_DATE = "definiteDueDate"; + private final static String IMPORTANCE = "importance"; + private final static String ID = "id"; + + private final static String TAGS_ID = "tags_id"; + + static String[] TASK_FIELD_LIST = new String[] { NAME, IMPORTANCE_COLOR, PREFERRED_DUE_DATE, DEFINITE_DUE_DATE, + IMPORTANCE, IDENTIFIER, TAGS_ID }; - static String[] TASK_FIELD_LIST = new String[] { AbstractTaskModel.NAME, IMPORTANCE_COLOR, - AbstractTaskModel.PREFERRED_DUE_DATE, AbstractTaskModel.DEFINITE_DUE_DATE, AbstractTaskModel.IMPORTANCE, IDENTIFIER }; + static String[] TAGS_FIELD_LIST = new String[] { ID, NAME }; private static final int URI_TASKS = 0; - // private static final int URI_MESSAGES = 1; - // private static final int URI_MESSAGE = 2; - // private static final int URI_FOLDERS = 3; + private static final int URI_TAGS = 1; + + private static final String TAG_SEPARATOR = "|"; private static Context ctx = null; static { URI_MATCHER.addURI(AUTHORITY, "tasks", URI_TASKS); - // URI_MATCHER.addURI(AUTHORITY, "messages/*", URI_MESSAGES); - // URI_MATCHER.addURI(AUTHORITY, "message/*", URI_MESSAGE); - // URI_MATCHER.addURI(AUTHORITY, "folders/*", URI_FOLDERS); + URI_MATCHER.addURI(AUTHORITY, "tags", URI_TAGS); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { - Log.d(TAG, "delete"); - - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // can only delete a message - - // List segments = null; - // String emailAccount = null; - // String msgId = null; - // String msgUId = null; - // - // segments = uri.getPathSegments(); - // emailAccount = segments.get(1); - // msgId = segments.get(2); - // - // - // openOrReopenDatabase(emailAccount); - // - // // get messages uid - // Cursor cursor = null; - // try { - // cursor = getAllMessages(null, "( id = " + msgId + " )", null, null); - // if (cursor != null) { - // cursor.moveToFirst(); - // msgUId = cursor.getString(cursor.getColumnIndex("uid")); - // cursor.close(); - // } - // } catch (Exception e) { - // e.printStackTrace(); - // } - // - // // get localstore parameter - // Message msg = null; - // try { - // Folder lf = LocalStore.getInstance(myAccount.getLocalStoreUri(), - // mApp, null).getFolder("INBOX"); - // int msgCount = lf.getMessageCount(); - // Log.d(TAG, "folder msg count = " + msgCount); - // msg = lf.getMessage(msgUId); - // } catch (MessagingException e) { - // e.printStackTrace(); - // } - // - // // launch command to delete the message - // if ((myAccount != null) && (msg != null)) { - // MessagingController.getInstance(mApp).deleteMessage(myAccount, - // "INBOX", msg, null); - // } - // - // notifyDatabaseModification(); + if (LOGD) + Log.d(TAG, "delete"); return 0; } @@ -120,15 +86,24 @@ public class TasksProvider extends ContentProvider { public Cursor getTags() { - // TaskController taskController = new TaskController(ctx); - // taskController.ge - // - // MatrixCursor ret = new MatrixCursor(TASK_FIELD_LIST); - // - // for (int i = 0; i < taskList.size(); i++) { - // } + LinkedList tags = null; - return null; + TagController tagController = new TagController(ctx); + tagController.open(); + tags = tagController.getAllTags(); + tagController.close(); + + MatrixCursor ret = new MatrixCursor(TAGS_FIELD_LIST); + + for (int i = 0; i < tags.size(); i++) { + Object[] values = new Object[2]; + values[0] = tags.get(i).getTagIdentifier().getId(); + values[1] = tags.get(i).getName(); + + ret.addRow(values); + } + + return ret; } public Cursor getTasks() { @@ -157,13 +132,28 @@ public class TasksProvider extends ContentProvider { if (taskModel.getDefiniteDueDate() != null) definiteDueDate = taskModel.getDefiniteDueDate().getTime(); - Object[] values = new Object[6]; + // get tags for task + LinkedList tags = null; + TagController tagController = new TagController(ctx); + tagController.open(); + tags = tagController.getTaskTags(taskModel.getTaskIdentifier()); + String taskTags = ""; + for (TagIdentifier tag : tags) { + if (taskTags.equals("")) + taskTags = Long.toString(tag.getId()); + else + taskTags = taskTags + TAG_SEPARATOR + Long.toString(tag.getId()); + } + tagController.close(); + + Object[] values = new Object[7]; values[0] = taskModel.getName(); values[1] = ctx.getResources().getColor(taskModel.getImportance().getColorResource()); values[2] = preferredDueDateTime; values[3] = definiteDueDate; values[4] = taskModel.getImportance().ordinal(); values[5] = taskModel.getTaskIdentifier().getId(); + values[6] = taskTags; ret.addRow(values); @@ -176,32 +166,19 @@ public class TasksProvider extends ContentProvider { @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - Log.d(TAG, "query"); + if (LOGD) + Log.d(TAG, "query"); Cursor cursor; switch (URI_MATCHER.match(uri)) { - // case URI_MESSAGES: - // segments = uri.getPathSegments(); - // emailAccount = segments.get(1); - // - // openOrReopenDatabase(emailAccount); - // - // cursor = getAllMessages(projection, selection, selectionArgs, - // sortOrder); - // break; case URI_TASKS: cursor = getTasks(); break; - // case URI_FOLDERS: - // segments = uri.getPathSegments(); - // emailAccount = segments.get(1); - // - // openOrReopenDatabase(emailAccount); - // - // cursor = getFolders(projection, selection, selectionArgs, sortOrder); - // break; + case URI_TAGS: + cursor = getTags(); + break; default: throw new IllegalStateException("Unrecognized URI:" + uri); @@ -210,93 +187,19 @@ public class TasksProvider extends ContentProvider { return cursor; } - // private void openOrReopenDatabase(String emailAccount) { - // - // String dbPath = null; - // - // if ((!emailAccount.equals(mCurrentEmailAccount)) || (mDb == null)) { - // - // // look at existing accounts - // for (Account account : - // Preferences.getPreferences(getContext()).getAccounts()) { - // if (account.getEmail().equals(emailAccount)) { - // dbPath = account.getLocalStoreUri(); - // } - // } - // - // if (dbPath != null) { - // - // // save this account as current account - // mCurrentEmailAccount = emailAccount; - // - // // close old database - // if (mDb != null) - // mDb.close(); - // - // // open database - // String path = Uri.parse(dbPath).getPath(); - // mDb = SQLiteDatabase.openDatabase(path, null, - // SQLiteDatabase.OPEN_READONLY); - // } - // } - // - // } - @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - Log.d(TAG, "update"); - - // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // // can only set flag to 'SEEN' - // - // List segments = null; - // String emailAccount = null; - // String msgId = null; - // String msgUId = null; - // - // segments = uri.getPathSegments(); - // emailAccount = segments.get(1); - // msgId = segments.get(2); - // - // openOrReopenDatabase(emailAccount); - // - // // get account parameters - // Account myAccount = null; - // for (Account account : - // Preferences.getPreferences(getContext()).getAccounts()) { - // if (emailAccount.equals(account.getEmail())) { - // myAccount = account; - // } - // } - // - // // get messages uid - // Cursor cursor = null; - // try { - // cursor = getAllMessages(null, "( id = " + msgId + " )", null, null); - // if (cursor != null) { - // cursor.moveToFirst(); - // msgUId = cursor.getString(cursor.getColumnIndex("uid")); - // cursor.close(); - // } - // } catch (Exception e) { - // e.printStackTrace(); - // } - // - // // launch command to delete the message - // if ((myAccount != null) && (msgUId != null)) { - // MessagingController.getInstance(mApp).markMessageRead(myAccount, - // "INBOX", msgUId, true); - // } - // - // notifyDatabaseModification(); + if (LOGD) + Log.d(TAG, "update"); return 0; } public static void notifyDatabaseModification() { - Log.d(TAG, "UPDATE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + if (LOGD) + Log.d(TAG, "notifyDatabaseModification"); ctx.getContentResolver().notifyChange(CONTENT_URI, null);