From a48ebe5d9c66a52842e506a72bd8629daed8e35f Mon Sep 17 00:00:00 2001 From: Tim Su Date: Sat, 16 Jan 2010 11:35:17 -0800 Subject: [PATCH] Merge from koxx3/astrid-provider ------------------------------------------------------------ Use --include-merges or -n0 to see merged revisions. --- .project | 78 +- AndroidManifest.xml | 28 +- res/values/strings.xml | 3 + src/com/timsu/astrid/activities/TaskEdit.java | 1716 ++++++++--------- .../astrid/data/task/TaskController.java | 30 + .../data/task/TaskModelForProvider.java | 73 + .../timsu/astrid/provider/TasksProvider.java | 305 +++ 7 files changed, 1323 insertions(+), 910 deletions(-) create mode 100644 src/com/timsu/astrid/data/task/TaskModelForProvider.java create mode 100644 src/com/timsu/astrid/provider/TasksProvider.java diff --git a/.project b/.project index a3142fce5..15252a590 100644 --- a/.project +++ b/.project @@ -1,39 +1,39 @@ - - - astrid-2.x - - - - - - org.eclipse.wst.common.project.facet.core.builder - - - - - com.android.ide.eclipse.adt.ResourceManagerBuilder - - - - - com.android.ide.eclipse.adt.PreCompilerBuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - com.android.ide.eclipse.adt.ApkBuilder - - - - - - com.android.ide.eclipse.adt.AndroidNature - org.eclipse.jdt.core.javanature - org.eclipse.wst.common.project.facet.core.nature - - + + + astrid-2.x + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + org.eclipse.wst.common.project.facet.core.nature + + diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a599e1474..c0ffe703c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -15,11 +15,19 @@ + + + + - + @@ -43,6 +51,13 @@ + + + + + + + @@ -105,6 +120,17 @@ + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index d3eb393b5..7ba2eaac9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -465,4 +465,7 @@ Astrid is the highly-acclaimed open-source task list that is simple enough to no Loading... + + Read Astrid tasks + diff --git a/src/com/timsu/astrid/activities/TaskEdit.java b/src/com/timsu/astrid/activities/TaskEdit.java index 4d24369b4..8058d0ef4 100644 --- a/src/com/timsu/astrid/activities/TaskEdit.java +++ b/src/com/timsu/astrid/activities/TaskEdit.java @@ -88,884 +88,860 @@ 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 * */ public class TaskEdit extends TaskModificationTabbedActivity { - // bundle arguments - public static final String TAG_NAME_TOKEN = "t"; - public static final String START_CHAR_TOKEN = "s"; - - // menu items - private static final int SAVE_ID = Menu.FIRST; - private static final int DISCARD_ID = Menu.FIRST + 1; - private static final int DELETE_ID = Menu.FIRST + 2; - - // other constants - private static final int MAX_TAGS = 5; - private static final int MAX_ALERTS = 5; - private static final String TAB_BASIC = "basic"; - private static final String TAB_DATES = "dates"; - private static final String TAB_ALERTS = "alerts"; - private static final int DEFAULT_CAL_TIME = 3600; - - // UI components - private EditText name; - private ImportanceControlSet importance; - private TimeDurationControlSet estimatedDuration; - private TimeDurationControlSet elapsedDuration; - private TimeDurationControlSet notification; - private DateControlSet definiteDueDate; - private DateControlSet preferredDueDate; - private DateControlSet hiddenUntil; - private EditText notes; - private LinearLayout tagsContainer; - private NotifyFlagControlSet flags; - private LinearLayout alertsContainer; - private Button repeatValue; - private Spinner repeatInterval; - private CheckBox addToCalendar; - - // other instance variables - private boolean shouldSaveState = true; - private boolean repeatHelpShown = false; - private TagController tagController; - private AlertController alertController; - private List tags; - private List taskTags; - - // OnClickListeners for save, discard and delete - private View.OnClickListener mSaveListener = new View.OnClickListener() { - public void onClick(View v) { - saveButtonClick(); - } - }; - private View.OnClickListener mDiscardListener = new View.OnClickListener() { - public void onClick(View v) { - discardButtonClick(); - } - }; - private View.OnClickListener mDeleteListener = new View.OnClickListener() { - public void onClick(View v) { - deleteButtonClick(); - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - tagController = new TagController(this); - tagController.open(); - alertController = new AlertController(this); - alertController.open(); - - TabHost tabHost = getTabHost(); - tabHost.setPadding(0, 4, 0, 0); - Resources r = getResources(); - - LayoutInflater.from(this).inflate(R.layout.task_edit, - tabHost.getTabContentView(), true); - - tabHost.addTab(tabHost.newTabSpec(TAB_BASIC) - .setIndicator(r.getString(R.string.taskEdit_tab_basic), - r.getDrawable(R.drawable.ic_dialog_info_c)) - .setContent(R.id.tab_basic)); - tabHost.addTab(tabHost.newTabSpec(TAB_DATES) - .setIndicator(r.getString(R.string.taskEdit_tab_dates), - r.getDrawable(R.drawable.ic_dialog_time_c)) - .setContent(R.id.tab_dates)); - tabHost.addTab(tabHost.newTabSpec(TAB_ALERTS) - .setIndicator(r.getString(R.string.taskEdit_tab_alerts), - r.getDrawable(R.drawable.ic_dialog_alert_c)) - .setContent(R.id.tab_notification)); - - setUpUIComponents(); + // bundle arguments + public static final String TAG_NAME_TOKEN = "t"; + public static final String START_CHAR_TOKEN = "s"; + + // menu items + private static final int SAVE_ID = Menu.FIRST; + private static final int DISCARD_ID = Menu.FIRST + 1; + private static final int DELETE_ID = Menu.FIRST + 2; + + // other constants + private static final int MAX_TAGS = 5; + private static final int MAX_ALERTS = 5; + private static final String TAB_BASIC = "basic"; + private static final String TAB_DATES = "dates"; + private static final String TAB_ALERTS = "alerts"; + private static final int DEFAULT_CAL_TIME = 3600; + + // UI components + private EditText name; + private ImportanceControlSet importance; + private TimeDurationControlSet estimatedDuration; + private TimeDurationControlSet elapsedDuration; + private TimeDurationControlSet notification; + private DateControlSet definiteDueDate; + private DateControlSet preferredDueDate; + private DateControlSet hiddenUntil; + private EditText notes; + private LinearLayout tagsContainer; + private NotifyFlagControlSet flags; + private LinearLayout alertsContainer; + private Button repeatValue; + private Spinner repeatInterval; + private CheckBox addToCalendar; + + // other instance variables + private boolean shouldSaveState = true; + private boolean repeatHelpShown = false; + private TagController tagController; + private AlertController alertController; + private List tags; + private List taskTags; + + // OnClickListeners for save, discard and delete + private View.OnClickListener mSaveListener = new View.OnClickListener() { + public void onClick(View v) { + saveButtonClick(); + } + }; + private View.OnClickListener mDiscardListener = new View.OnClickListener() { + public void onClick(View v) { + discardButtonClick(); + } + }; + private View.OnClickListener mDeleteListener = new View.OnClickListener() { + public void onClick(View v) { + deleteButtonClick(); + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + long taskId = 0; + try { + taskId = intent.getExtras().getLong("id"); + } catch (Exception e) { + e.printStackTrace(); + } +// Log.d("astrid", "id = " + taskId); + + tagController = new TagController(this); + tagController.open(); + alertController = new AlertController(this); + alertController.open(); + + TabHost tabHost = getTabHost(); + tabHost.setPadding(0, 4, 0, 0); + Resources r = getResources(); + + LayoutInflater.from(this).inflate(R.layout.task_edit, tabHost.getTabContentView(), true); + + tabHost.addTab(tabHost.newTabSpec(TAB_BASIC).setIndicator(r.getString(R.string.taskEdit_tab_basic), + r.getDrawable(R.drawable.ic_dialog_info_c)).setContent(R.id.tab_basic)); + tabHost.addTab(tabHost.newTabSpec(TAB_DATES).setIndicator(r.getString(R.string.taskEdit_tab_dates), + r.getDrawable(R.drawable.ic_dialog_time_c)).setContent(R.id.tab_dates)); + tabHost.addTab(tabHost.newTabSpec(TAB_ALERTS).setIndicator(r.getString(R.string.taskEdit_tab_alerts), + r.getDrawable(R.drawable.ic_dialog_alert_c)).setContent(R.id.tab_notification)); + + setUpUIComponents(); setUpListeners(); // 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/task/TaskController.java b/src/com/timsu/astrid/data/task/TaskController.java index 3ed2c8ac6..63bfec8b6 100644 --- a/src/com/timsu/astrid/data/task/TaskController.java +++ b/src/com/timsu/astrid/data/task/TaskController.java @@ -44,6 +44,7 @@ import com.timsu.astrid.data.alerts.AlertController; import com.timsu.astrid.data.sync.SyncDataController; import com.timsu.astrid.data.task.AbstractTaskModel.RepeatInfo; import com.timsu.astrid.data.task.AbstractTaskModel.TaskModelDatabaseHelper; +import com.timsu.astrid.provider.TasksProvider; import com.timsu.astrid.sync.Synchronizer; import com.timsu.astrid.sync.Synchronizer.SynchronizerListener; import com.timsu.astrid.utilities.Notifications; @@ -220,6 +221,10 @@ public class TaskController extends AbstractController { throw new UnsupportedOperationException("Cannot delete uncreated task!"); long id = taskId.getId(); cleanupTask(taskId, false); + + // notify modification + TasksProvider.notifyDatabaseModification(); + return database.delete(TASK_TABLE_NAME, KEY_ROWID + "=" + id, null) > 0; } @@ -264,6 +269,9 @@ public class TaskController extends AbstractController { context.startService(intent); } + // notify modification + TasksProvider.notifyDatabaseModification(); + return saveSucessful; } @@ -531,6 +539,28 @@ public class TaskController extends AbstractController { } } + public ArrayList getTasksForProvider(String limit) { + + Cursor cursor = database.query(TASK_TABLE_NAME, TaskModelForWidget.FIELD_LIST, + AbstractTaskModel.PROGRESS_PERCENTAGE + " < " + + AbstractTaskModel.COMPLETE_PERCENTAGE + " AND (" + + AbstractTaskModel.HIDDEN_UNTIL + " ISNULL OR " + AbstractTaskModel.HIDDEN_UNTIL + " < " + + System.currentTimeMillis() + ")", null, null, null, + AbstractTaskModel.IMPORTANCE + " * " + (5 * 24 * 3600 * 1000L) + + " + CASE WHEN MAX(pdd, ddd) = 0 THEN " + + (System.currentTimeMillis() + (7 * 24 * 3600 * 1000L)) + + " ELSE (CASE WHEN pdd = 0 THEN ddd ELSE pdd END) END ASC", limit); + + try { + ArrayList list = new ArrayList(); + for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) + list.add(new TaskModelForProvider(cursor)); + return list; + } finally { + cursor.close(); + } + } + // --- boilerplate /** diff --git a/src/com/timsu/astrid/data/task/TaskModelForProvider.java b/src/com/timsu/astrid/data/task/TaskModelForProvider.java new file mode 100644 index 000000000..b9d1cf5b8 --- /dev/null +++ b/src/com/timsu/astrid/data/task/TaskModelForProvider.java @@ -0,0 +1,73 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Francois DESLANDES + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.timsu.astrid.data.task; + +import java.util.Date; + +import android.database.Cursor; + +import com.timsu.astrid.data.AbstractController; +import com.timsu.astrid.data.enums.Importance; + + + +/** Fields that you would want to see in the TaskView activity */ +public class TaskModelForProvider extends AbstractTaskModel { + + static String[] FIELD_LIST = new String[] { + AbstractController.KEY_ROWID, + NAME, + IMPORTANCE, + PREFERRED_DUE_DATE, + DEFINITE_DUE_DATE, + "COALESCE(" + PREFERRED_DUE_DATE + ", 0) as pdd", + "COALESCE(" + DEFINITE_DUE_DATE + ", 0) as ddd" + }; + + // --- constructors + + public TaskModelForProvider(Cursor cursor) { + super(cursor); + + prefetchData(FIELD_LIST); + } + + // --- getters + + @Override + public String getName() { + return super.getName(); + } + + @Override + public Importance getImportance() { + return super.getImportance(); + } + + @Override + public Date getPreferredDueDate() { + return super.getPreferredDueDate(); + } + + @Override + public Date getDefiniteDueDate() { + return super.getDefiniteDueDate(); + } +} diff --git a/src/com/timsu/astrid/provider/TasksProvider.java b/src/com/timsu/astrid/provider/TasksProvider.java new file mode 100644 index 000000000..299c441a8 --- /dev/null +++ b/src/com/timsu/astrid/provider/TasksProvider.java @@ -0,0 +1,305 @@ +package com.timsu.astrid.provider; + +import java.util.ArrayList; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.util.Log; + +import com.timsu.astrid.data.task.AbstractTaskModel; +import com.timsu.astrid.data.task.TaskController; +import com.timsu.astrid.data.task.TaskModelForProvider; + +public class TasksProvider extends ContentProvider { + + private static final String TAG = "MessageProvider"; + + 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 final static String IMPORTANCE_COLOR = "importance_color"; + private final static String IDENTIFIER = "identifier"; + + static String[] TASK_FIELD_LIST = new String[] { AbstractTaskModel.NAME, IMPORTANCE_COLOR, + AbstractTaskModel.PREFERRED_DUE_DATE, AbstractTaskModel.DEFINITE_DUE_DATE, AbstractTaskModel.IMPORTANCE, IDENTIFIER }; + + 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 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); + } + + @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(); + + return 0; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public boolean onCreate() { + ctx = getContext(); + return false; + } + + 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++) { + // } + + return null; + } + + public Cursor getTasks() { + + int numberOfTasks = MAX_NUMBEER_OF_TASKS; + + TaskController taskController = new TaskController(ctx); + taskController.open(); + ArrayList taskList = taskController.getTasksForProvider(Integer.toString(numberOfTasks)); + taskController.close(); + + MatrixCursor ret = new MatrixCursor(TASK_FIELD_LIST); + + for (int i = 0; i < taskList.size(); i++) { + TaskModelForProvider taskModel = taskList.get(i); + + if (taskModel != null) { + + // get prefered due date time + long preferredDueDateTime = 0; + if (taskModel.getPreferredDueDate() != null) + preferredDueDateTime = taskModel.getPreferredDueDate().getTime(); + + // get definite due date time + long definiteDueDate = 0; + if (taskModel.getDefiniteDueDate() != null) + definiteDueDate = taskModel.getDefiniteDueDate().getTime(); + + Object[] values = new Object[6]; + 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(); + + ret.addRow(values); + + } + } + + return ret; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + + 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; + + default: + throw new IllegalStateException("Unrecognized URI:" + uri); + } + + 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(); + + return 0; + } + + public static void notifyDatabaseModification() { + + Log.d(TAG, "UPDATE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + ctx.getContentResolver().notifyChange(CONTENT_URI, null); + + } + +}