Try a bit harder to ensure timezones get added when dates referencing them are.
[acal.git] / src / com / morphoss / acal / activity / TodoEdit.java
blobfe11e5ca25a77a60c1aa182051f8a6f469388647
1 /*
2 * Copyright (C) 2011 Morphoss Ltd
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 package com.morphoss.acal.activity;
21 import java.util.List;
23 import android.app.Activity;
24 import android.app.AlertDialog;
25 import android.app.Dialog;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.Intent;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.Message;
33 import android.util.Log;
34 import android.view.LayoutInflater;
35 import android.view.View;
36 import android.view.View.OnClickListener;
37 import android.view.ViewGroup;
38 import android.widget.AdapterView;
39 import android.widget.AdapterView.OnItemSelectedListener;
40 import android.widget.ArrayAdapter;
41 import android.widget.Button;
42 import android.widget.CompoundButton;
43 import android.widget.CompoundButton.OnCheckedChangeListener;
44 import android.widget.ImageView;
45 import android.widget.LinearLayout;
46 import android.widget.RelativeLayout;
47 import android.widget.SeekBar;
48 import android.widget.SeekBar.OnSeekBarChangeListener;
49 import android.widget.Spinner;
50 import android.widget.TableLayout;
51 import android.widget.TableRow;
52 import android.widget.TextView;
53 import android.widget.Toast;
55 import com.morphoss.acal.AcalTheme;
56 import com.morphoss.acal.Constants;
57 import com.morphoss.acal.PrefNames;
58 import com.morphoss.acal.R;
59 import com.morphoss.acal.StaticHelpers;
60 import com.morphoss.acal.acaltime.AcalDateTime;
61 import com.morphoss.acal.acaltime.AcalDateTimeFormatter;
62 import com.morphoss.acal.acaltime.AcalDuration;
63 import com.morphoss.acal.acaltime.AcalRepeatRule;
64 import com.morphoss.acal.database.cachemanager.CacheObject;
65 import com.morphoss.acal.database.resourcesmanager.ResourceChangedEvent;
66 import com.morphoss.acal.database.resourcesmanager.ResourceChangedListener;
67 import com.morphoss.acal.database.resourcesmanager.ResourceManager;
68 import com.morphoss.acal.database.resourcesmanager.ResourceResponse;
69 import com.morphoss.acal.database.resourcesmanager.ResourceResponseListener;
70 import com.morphoss.acal.database.resourcesmanager.requests.RRRequestInstance;
71 import com.morphoss.acal.dataservice.CalendarInstance;
72 import com.morphoss.acal.dataservice.Collection;
73 import com.morphoss.acal.dataservice.TodoInstance;
74 import com.morphoss.acal.davacal.AcalAlarm;
75 import com.morphoss.acal.davacal.AcalAlarm.ActionType;
76 import com.morphoss.acal.davacal.AcalAlarm.RelateWith;
77 import com.morphoss.acal.davacal.PropertyName;
78 import com.morphoss.acal.davacal.VCalendar;
79 import com.morphoss.acal.davacal.VComponent;
80 import com.morphoss.acal.davacal.VTodo;
81 import com.morphoss.acal.providers.DavCollections;
82 import com.morphoss.acal.service.aCalService;
83 import com.morphoss.acal.widget.AlarmDialog;
84 import com.morphoss.acal.widget.DateTimeDialog;
85 import com.morphoss.acal.widget.DateTimeSetListener;
87 @SuppressWarnings("rawtypes")
88 public class TodoEdit extends AcalActivity
89 implements OnCheckedChangeListener, OnSeekBarChangeListener,
90 ResourceChangedListener, ResourceResponseListener {
92 public static final String TAG = "aCal TodoEdit";
94 private VTodo todo;
96 public static final int ACTION_NONE = -1;
97 public static final int ACTION_CREATE = 0;
98 public static final int ACTION_MODIFY_SINGLE = 1;
99 public static final int ACTION_MODIFY_ALL = 2;
100 public static final int ACTION_MODIFY_ALL_FUTURE = 3;
101 public static final int ACTION_DELETE_SINGLE = 4;
102 public static final int ACTION_DELETE_ALL = 5;
103 public static final int ACTION_DELETE_ALL_FUTURE = 6;
104 public static final int ACTION_COMPLETE = 7;
105 public static final int ACTION_EDIT = 8;
106 public static final int ACTION_COPY = 9;
108 private int action = ACTION_NONE;
110 private static final int FROM_DIALOG = 10;
111 private static final int DUE_DIALOG = 11;
112 private static final int COMPLETED_DIALOG = 12;
113 private static final int ADD_ALARM_DIALOG = 20;
114 private static final int SET_REPEAT_RULE_DIALOG = 21;
115 private static final int INSTANCES_TO_CHANGE_DIALOG = 30;
116 private static final int LOADING_DIALOG = 0xfeed;
117 private static final int SAVING_DIALOG = 0xbeef;
119 boolean prefer24hourFormat = false;
121 private String[] repeatRules;
122 private String[] todoChangeRanges; // See strings.xml R.array.TodoChangeAffecting
124 private String[] alarmRelativeTimeStrings;
125 // Must match R.array.RelativeAlarmTimes (strings.xml)
126 public static final AcalDuration[] alarmValues = new AcalDuration[] {
127 new AcalDuration(),
128 new AcalDuration("-PT10M"),
129 new AcalDuration("-PT15M"),
130 new AcalDuration("-PT30M"),
131 new AcalDuration("-PT1H"),
132 new AcalDuration("-PT2H"),
133 //** Custom **//
136 public static final String KEY_CACHE_OBJECT = "CacheObject";
137 public static final String KEY_OPERATION = "Operation";
138 public static final String KEY_RESOURCE = "Resource";
139 public static final String KEY_VCALENDAR_BLOB = "VCalendar";
142 private String[] repeatRulesValues;
144 //GUI Components
145 private Button btnStartDate;
146 private Button btnDueDate;
147 private Button btnCompleteDate;
148 private LinearLayout sidebar;
149 private LinearLayout sidebarBottom;
150 private TextView todoName;
151 private TextView locationView;
152 private TextView notesView;
153 private TableLayout alarmsList;
154 private LinearLayout repeatsLayout;
155 private Button btnAddRepeat;
156 private RelativeLayout alarmsLayout;
157 private Button btnAddAlarm;
158 private LinearLayout collectionsLayout;
159 private Spinner spinnerCollection;
160 private Button btnSaveChanges;
161 private Button btnCancelChanges;
164 private int percentComplete = 0;
165 private SeekBar percentCompleteBar;
166 private TextView percentCompleteText;
168 //Active collections for create mode
169 private Collection currentCollection; //currently selected collection
170 private CollectionForArrayAdapter[] collectionsArray;
172 private List<AcalAlarm> alarmList;
174 private boolean originalHasOccurrence = false;
175 private String originalOccurence = "";
177 private int currentOperation;
178 private static final int REFRESH = 0;
179 private static final int FAIL = 1;
180 private static final int CONFLICT = 2;
181 private static final int SHOW_LOADING = 3;
182 private static final int GIVE_UP = 4;
183 private static final int SAVE_RESULT = 5;
184 private static final int SAVE_FAILED = 6;
185 private static final int SHOW_SAVING = 7;
187 private Dialog loadingDialog = null;
188 private ResourceManager resourceManager;
190 private Dialog savingDialog = null;
192 private long rid = -1;
194 private boolean saveSucceeded = false;
195 private boolean isSaving = false;
196 private boolean isLoading = false;
198 private static TodoEdit handlerContext = null;
200 private static Handler mHandler = new Handler() {
201 public void handleMessage(Message msg) {
202 if ( handlerContext != null ) handlerContext.messageHandler(msg);
207 private void messageHandler(Message msg) {
208 switch( msg.what ) {
209 case REFRESH:
210 if ( loadingDialog != null ) {
211 loadingDialog.dismiss();
212 loadingDialog = null;
214 updateLayout();
215 break;
217 case CONFLICT:
218 Toast.makeText(
219 TodoEdit.this,
220 "The resource you are editing has been changed or deleted on the server.",
221 Toast.LENGTH_LONG).show();
222 break;
223 case SHOW_LOADING:
224 if ( todo == null ) showDialog(LOADING_DIALOG);
225 break;
227 case FAIL:
228 if ( isLoading ) {
229 Toast.makeText(TodoEdit.this, "Error loading data.", Toast.LENGTH_LONG).show();
230 isLoading = false;
232 else if ( isSaving ) {
233 isSaving = false;
234 if ( savingDialog != null ) savingDialog
235 .dismiss();
236 Toast.makeText( TodoEdit.this, "Something went wrong trying to save data.", Toast.LENGTH_LONG).show();
237 setResult(Activity.RESULT_CANCELED, null);
239 finish();
240 break;
241 case GIVE_UP:
242 if ( loadingDialog != null ) {
243 loadingDialog.dismiss();
244 Toast.makeText(
245 TodoEdit.this,
246 "Error loading event data.",
247 Toast.LENGTH_LONG).show();
248 finish();
249 return;
251 break;
253 case SHOW_SAVING:
254 isSaving = true;
255 showDialog(SAVING_DIALOG);
256 break;
258 case SAVE_RESULT:
259 // dismiss
260 // dialog
261 mHandler.removeMessages(SAVE_FAILED);
262 isSaving = false;
263 if ( savingDialog != null ) savingDialog.dismiss();
264 long res = (Long) msg.obj;
265 if ( res >= 0 ) {
266 Intent ret = new Intent();
267 Bundle b = new Bundle();
268 ret.putExtras(b);
269 setResult(RESULT_OK, ret);
270 saveSucceeded = true;
272 finish();
275 else {
276 Toast.makeText(TodoEdit.this, "Error saving event data.", Toast.LENGTH_LONG).show();
278 break;
280 case SAVE_FAILED:
281 isSaving = false;
282 if ( savingDialog != null ) savingDialog.dismiss();
283 if ( saveSucceeded ) {
284 // Don't know why we get here, but we do! - cancel save failed when save succeeds.
285 // we shouldn't see this anymore.
286 Log.w(TAG, "This should have been fixed now yay!", new Exception());
288 else {
289 Toast.makeText( TodoEdit.this, "Something went wrong trying to save data.", Toast.LENGTH_LONG).show();
290 setResult(Activity.RESULT_CANCELED, null);
291 finish();
293 break;
297 public void onCreate(Bundle savedInstanceState) {
298 super.onCreate(savedInstanceState);
299 this.setContentView(R.layout.todo_edit);
301 //Ensure service is actually running
302 startService(new Intent(this, aCalService.class));
304 ContentValues[] taskCollections = DavCollections.getCollections( getContentResolver(), DavCollections.INCLUDE_TASKS );
305 if ( taskCollections.length == 0 ) {
306 Toast.makeText(this, getString(R.string.errorMustHaveActiveCalendar), Toast.LENGTH_LONG).show();
307 this.finish(); // can't work if no active collections
308 return;
311 this.collectionsArray = new CollectionForArrayAdapter[taskCollections.length];
312 int count = 0;
313 long collectionId;
314 for (ContentValues cv : taskCollections ) {
315 collectionId = cv.getAsLong(DavCollections._ID);
316 collectionsArray[count++] = new CollectionForArrayAdapter(this,collectionId);
320 resourceManager = ResourceManager.getInstance(this,this);
321 todo = null;
322 requestTodoResource();
324 // Get time display preference
325 prefer24hourFormat = prefs.getBoolean(getString(R.string.prefTwelveTwentyfour), false);
327 alarmRelativeTimeStrings = getResources().getStringArray(R.array.RelativeAlarmTimes);
328 todoChangeRanges = getResources().getStringArray(R.array.TodoChangeAffecting);
330 this.populateLayout();
331 if ( this.todo != null ) this.updateLayout();
334 @Override
335 public void onDestroy() {
336 if ( handlerContext == this ) handlerContext = null;
337 super.onDestroy();
340 @SuppressWarnings("unchecked")
341 private void requestTodoResource() {
342 currentOperation = ACTION_CREATE;
343 try {
344 Bundle b = this.getIntent().getExtras();
345 if ( b != null && b.containsKey(KEY_OPERATION) ) {
346 currentOperation = b.getInt(KEY_OPERATION);
348 if ( b != null && b.containsKey(KEY_CACHE_OBJECT) ) {
349 if ( currentOperation == ACTION_CREATE ) currentOperation = ACTION_EDIT;
350 CacheObject cacheTodo = (CacheObject) b.getParcelable(KEY_CACHE_OBJECT);
351 this.rid = cacheTodo.getResourceId();
352 handlerContext = this;
353 resourceManager.sendRequest(new RRRequestInstance(this, rid, cacheTodo.getRecurrenceId()));
354 mHandler.sendMessageDelayed(mHandler.obtainMessage(SHOW_LOADING), 50);
355 mHandler.sendMessageDelayed(mHandler.obtainMessage(GIVE_UP), 10000);
358 catch (Exception e) {
359 Log.e(TAG, "No bundle from caller.", e);
362 if ( currentOperation == ACTION_CREATE ) {
363 long preferredCollectionId = -1;
364 try {
365 preferredCollectionId = Long.parseLong(prefs.getString(PrefNames.defaultTasksCollection, "-1"));
367 catch( Exception e ) {}
368 if ( preferredCollectionId == -1 || Collection.getInstance(preferredCollectionId, this) == null )
369 preferredCollectionId = collectionsArray[0].getCollectionId();
371 currentCollection = Collection.getInstance(preferredCollectionId, this);
373 this.todo = new VTodo();
374 this.action = ACTION_CREATE;
375 this.todo.setPercentComplete(0);
380 private void setTodo( VTodo newTodo ) {
381 this.todo = newTodo;
382 long collectionId = -1;
383 if ( currentOperation == ACTION_EDIT ) {
384 this.action = ACTION_MODIFY_ALL;
385 if ( isModifyAction() ) {
386 String rr = (String) this.todo.getRRule();
387 if (rr != null && !rr.equals("") && !rr.equals(AcalRepeatRule.SINGLE_INSTANCE)) {
388 this.originalHasOccurrence = true;
389 this.originalOccurence = rr;
391 if (this.originalHasOccurrence) {
392 this.action = ACTION_MODIFY_SINGLE;
394 else {
395 this.action = ACTION_MODIFY_ALL;
399 else if ( currentOperation == ACTION_COPY ) {
400 this.action = ACTION_CREATE;
403 if ( Collection.getInstance(collectionId,this) != null )
404 currentCollection = Collection.getInstance(collectionId,this);
406 if ( todo.getCompleted() != null ) todo.setPercentComplete( 100 );
411 * The ArrayAdapter needs something which can return a displayed value on toString() and it's
412 * not really reasonable to add that sort of oddity to Collection itself.
414 private class CollectionForArrayAdapter {
415 Collection c;
416 public CollectionForArrayAdapter(Context cx, long id) {
417 c = Collection.getInstance(id, cx);
420 public long getCollectionId() {
421 return c.getCollectionId();
424 public String toString() {
425 return c.getDisplayName();
431 * Populate the screen initially.
433 private void populateLayout() {
435 //Sidebar
436 sidebar = (LinearLayout)this.findViewById(R.id.TodoEditColourBar);
437 sidebarBottom = (LinearLayout)this.findViewById(R.id.EventEditColourBarBottom);
439 //Title
440 this.todoName = (TextView) this.findViewById(R.id.TodoName);
441 todoName.setSelectAllOnFocus(action == ACTION_CREATE);
443 //Collection
444 collectionsLayout = (LinearLayout)this.findViewById(R.id.TodoCollectionLayout);
445 spinnerCollection = (Spinner) this.findViewById(R.id.TodoEditCollectionSelect);
446 if (collectionsArray.length < 2) {
447 spinnerCollection.setEnabled(false);
448 collectionsLayout.setVisibility(View.GONE);
450 else {
451 spinnerCollection.setOnItemSelectedListener( new OnItemSelectedListener() {
453 @Override
454 public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
455 setSelectedCollection(collectionsArray[arg2].getCollectionId());
458 @Override
459 public void onNothingSelected(AdapterView<?> arg0) {
466 //date/time fields
467 btnStartDate = (Button) this.findViewById(R.id.TodoFromDateTime);
468 btnDueDate = (Button) this.findViewById(R.id.TodoDueDateTime);
469 btnCompleteDate = (Button) this.findViewById(R.id.TodoCompletedDateTime);
471 btnSaveChanges = (Button) this.findViewById(R.id.todo_apply_button);
472 btnCancelChanges = (Button) this.findViewById(R.id.todo_cancel_button);
475 locationView = (TextView) this.findViewById(R.id.TodoLocationContent);
476 notesView = (TextView) this.findViewById(R.id.TodoNotesContent);
478 alarmsLayout = (RelativeLayout) this.findViewById(R.id.TodoAlarmsLayout);
479 alarmsList = (TableLayout) this.findViewById(R.id.alarms_list_table);
480 btnAddAlarm = (Button) this.findViewById(R.id.TodoAlarmsButton);
482 repeatsLayout = (LinearLayout) this.findViewById(R.id.TodoRepeatsLayout);
483 btnAddRepeat = (Button) this.findViewById(R.id.TodoRepeatsContent);
485 // Button listeners
486 setButtonDialog(btnStartDate, FROM_DIALOG);
487 setButtonDialog(btnDueDate, DUE_DIALOG);
488 setButtonDialog(btnCompleteDate, COMPLETED_DIALOG);
489 setButtonDialog(btnAddAlarm, ADD_ALARM_DIALOG);
490 setButtonDialog(btnAddRepeat, SET_REPEAT_RULE_DIALOG);
492 AcalTheme.setContainerFromTheme(btnSaveChanges, AcalTheme.BUTTON);
493 AcalTheme.setContainerFromTheme(btnCancelChanges, AcalTheme.BUTTON);
495 btnSaveChanges.setOnClickListener(new View.OnClickListener() {
496 public void onClick(View arg0) {
497 applyChanges();
501 btnCancelChanges.setOnClickListener(new View.OnClickListener() {
502 public void onClick(View arg0) {
503 finish();
508 percentCompleteText = (TextView) this.findViewById(R.id.TodoPercentCompleteText);
509 percentCompleteBar = (SeekBar) this.findViewById(R.id.TodoPercentCompleteBar);
510 percentCompleteBar.setIndeterminate(false);
511 percentCompleteBar.setMax(100);
512 percentCompleteBar.setKeyProgressIncrement(5);
513 percentCompleteBar.setOnSeekBarChangeListener(this);
514 percentCompleteText.setText(Integer.toString(percentComplete)+"%");
515 percentCompleteBar.setProgress(percentComplete);
521 * Update the screen whenever something has changed.
523 private void updateLayout() {
524 AcalDateTime start = todo.getStart();
525 AcalDateTime due = todo.getDue();
526 AcalDateTime completed = todo.getCompleted();
528 percentComplete = todo.getPercentComplete();
529 percentComplete = (percentComplete < 0 ? 0 : (percentComplete > 100 ? 100 : percentComplete));
530 String title = todo.getSummary();
531 todoName.setText(title);
533 String location = todo.getLocation();
534 locationView.setText(location);
536 String description = todo.getDescription();
537 notesView.setText(description);
539 Integer colour = (currentCollection == null ? 0x808080c0 : currentCollection.getColour());
540 if ( colour == null ) colour = 0x70a0a0a0;
541 sidebar.setBackgroundColor(colour);
542 sidebarBottom.setBackgroundColor(colour);
543 todoName.setTextColor(colour);
544 AcalTheme.setContainerColour(spinnerCollection,colour);
546 ArrayAdapter<CollectionForArrayAdapter> collectionAdapter =
547 new ArrayAdapter<CollectionForArrayAdapter>(this,android. R.layout.select_dialog_item, collectionsArray);
548 int spinnerPosition = 0;
549 while( spinnerPosition < collectionsArray.length &&
550 collectionsArray[spinnerPosition].getCollectionId() != currentCollection.getCollectionId())
551 spinnerPosition++;
553 spinnerCollection.setAdapter(collectionAdapter);
554 if ( spinnerPosition < collectionsArray.length )
555 //set the default according to value
556 spinnerCollection.setSelection(spinnerPosition);
558 try {
559 // Attempt to set text colour that works with (hopefully) background colour.
560 for( View v : StaticHelpers.getViewsInside(spinnerCollection, TextView.class) ) {
561 ((TextView) v).setTextColor(AcalTheme.pickForegroundForBackground(colour));
562 ((TextView) v).setMaxLines(1);
565 catch( Exception e ) {
566 // Oh well. Some other way then... @journal.
567 Log.i(TAG,"Think of another solution...",e);
570 btnSaveChanges.setText((isModifyAction() ? getString(R.string.Apply) : getString(R.string.Add)));
572 btnStartDate.setText( AcalDateTimeFormatter.fmtFull( start, prefer24hourFormat) );
573 btnDueDate.setText( AcalDateTimeFormatter.fmtFull( due, prefer24hourFormat) );
574 btnCompleteDate.setText( AcalDateTimeFormatter.fmtFull( completed, prefer24hourFormat) );
576 if ( start != null && due != null && due.before(start) ) {
577 AcalTheme.setContainerColour(btnStartDate,0xffff3030);
579 else {
580 AcalTheme.setContainerFromTheme(btnStartDate, AcalTheme.BUTTON);
583 if ( start == null && due == null ) {
584 alarmList = todo.getAlarms();
585 this.alarmsList.removeAllViews();
586 alarmsLayout.setVisibility(View.GONE);
588 else {
589 //Display Alarms
590 alarmList = todo.getAlarms();
591 this.alarmsList.removeAllViews();
592 for (AcalAlarm alarm : alarmList) {
593 this.alarmsList.addView(this.getAlarmItem(alarm, alarmsList));
595 alarmsLayout.setVisibility(View.VISIBLE);
598 //set repeat options
599 if ( start == null && due == null ) {
600 repeatsLayout.setVisibility(View.GONE);
602 else {
603 AcalDateTime relativeTo = (start == null ? due : start);
604 int dow = relativeTo.getWeekDay();;
605 int weekNum = relativeTo.getMonthWeek();
607 String dowStr = "";
608 String dowLongString = "";
609 String everyDowString = "";
610 switch (dow) {
611 case 0:
612 dowStr="MO";
613 dowLongString = getString(R.string.Monday);
614 everyDowString = getString(R.string.EveryMonday);
615 break;
616 case 1:
617 dowStr="TU";
618 dowLongString = getString(R.string.Tuesday);
619 everyDowString = getString(R.string.EveryTuesday);
620 break;
621 case 2:
622 dowStr="WE";
623 dowLongString = getString(R.string.Wednesday);
624 everyDowString = getString(R.string.EveryWednesday);
625 break;
626 case 3:
627 dowStr="TH";
628 dowLongString = getString(R.string.Thursday);
629 everyDowString = getString(R.string.EveryThursday);
630 break;
631 case 4:
632 dowStr="FR";
633 dowLongString = getString(R.string.Friday);
634 everyDowString = getString(R.string.EveryFriday);
635 break;
636 case 5:
637 dowStr="SA";
638 dowLongString = getString(R.string.Saturday);
639 everyDowString = getString(R.string.EverySaturday);
640 break;
641 case 6:
642 dowStr="SU";
643 dowLongString = getString(R.string.Sunday);
644 everyDowString = getString(R.string.EverySunday);
645 break;
647 String dailyRepeatName = getString(R.string.EveryWeekday);
648 String dailyRepeatRule = "FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;COUNT=260";
649 if (relativeTo.get(AcalDateTime.DAY_OF_WEEK) == AcalDateTime.SATURDAY || relativeTo.get(AcalDateTime.DAY_OF_WEEK) == AcalDateTime.SUNDAY) {
650 dailyRepeatName = getString(R.string.EveryWeekend);
651 dailyRepeatRule = "FREQ=WEEKLY;BYDAY=SA,SU;COUNT=104";
654 this.repeatRules = new String[] {
655 getString(R.string.OnlyOnce),
656 getString(R.string.EveryDay),
657 dailyRepeatName,
658 everyDowString,
659 String.format(this.getString(R.string.EveryNthOfTheMonth),
660 relativeTo.getMonthDay()+AcalDateTime.getSuffix(relativeTo.getMonthDay())),
661 String.format(this.getString(R.string.EveryMonthOnTheNthSomeday),
662 weekNum+AcalDateTime.getSuffix(weekNum)+" "+dowLongString),
663 getString(R.string.EveryYear)
665 this.repeatRulesValues = new String[] {
666 "FREQ=DAILY;COUNT=400",
667 dailyRepeatRule,
668 "FREQ=WEEKLY;BYDAY="+dowStr,
669 "FREQ=MONTHLY;COUNT=60",
670 "FREQ=MONTHLY;COUNT=60;BYDAY="+weekNum+dowStr,
671 "FREQ=YEARLY"
673 String repeatRuleString = todo.getRRule();
674 if (repeatRuleString == null) repeatRuleString = "";
675 AcalRepeatRule RRule;
676 try {
677 RRule = new AcalRepeatRule(relativeTo, repeatRuleString);
679 catch( IllegalArgumentException e ) {
680 Log.i(TAG,"Illegal repeat rule: '"+repeatRuleString+"'");
681 RRule = new AcalRepeatRule(relativeTo, null );
683 String rr = RRule.repeatRule.toPrettyString(this);
684 if (rr == null || rr.equals("")) rr = getString(R.string.OnlyOnce);
685 btnAddRepeat.setText(rr);
686 repeatsLayout.setVisibility(View.VISIBLE);
691 private void setSelectedCollection(long collectionId) {
693 if ( Collection.getInstance(collectionId,this) != null && (currentCollection == null || collectionId != currentCollection.collectionId) ) {
694 currentCollection = Collection.getInstance(collectionId,this);
695 this.updateLayout();
700 private void setButtonDialog(Button myButton, final int dialogIndicator) {
701 myButton.setOnClickListener(new View.OnClickListener() {
703 @Override
704 public void onClick(View arg0) {
705 showDialog(dialogIndicator);
708 AcalTheme.setContainerFromTheme(myButton, AcalTheme.BUTTON);
712 public void applyChanges() {
713 //check if text fields changed
714 //summary
715 String oldSum = todo.getSummary();
716 String newSum = this.todoName.getText().toString() ;
717 String oldLoc = todo.getLocation();
718 String newLoc = this.locationView.getText().toString();
719 String oldDesc = todo.getDescription();
720 String newDesc = this.notesView.getText().toString() ;
722 if (!oldSum.equals(newSum)) todo.setSummary(newSum);
723 if (!oldLoc.equals(newLoc)) todo.setLocation(newLoc);
724 if (!oldDesc.equals(newDesc)) todo.setDescription(newDesc);
726 todo.setPercentComplete(percentComplete);
728 if (action == ACTION_CREATE || action == ACTION_MODIFY_ALL ) {
729 if ( !this.saveChanges() ){
730 Toast.makeText(this, "Save failed: retrying!", Toast.LENGTH_LONG).show();
731 this.saveChanges();
734 finish();
737 @SuppressWarnings("unchecked")
738 private boolean saveChanges() {
740 try {
741 VCalendar vc = (VCalendar) todo.getTopParent();
742 vc.updateTimeZones();
744 AcalDateTime dtStart = todo.getStart();
745 if ( Constants.LOG_DEBUG ) Log.println(Constants.LOGD, TAG,
746 "saveChanges: "+todo.getSummary()+
747 ", starts "+(dtStart == null ? "not set" : dtStart.toPropertyString(PropertyName.DTSTART)));
749 int sendAction = RRResourceEditedRequest.ACTION_UPDATE;
750 if (action == ACTION_CREATE ) sendAction = RRResourceEditedRequest.ACTION_CREATE;
751 else if (action == ACTION_DELETE_ALL ) sendAction = RRResourceEditedRequest.ACTION_DELETE;
752 ResourceManager.getInstance(this)
753 .sendRequest(new RRResourceEditedRequest(this, currentCollection.collectionId, rid, vc, sendAction));
755 handlerContext = this;
757 //set message for 10 seconds to fail.
758 mHandler.sendEmptyMessageDelayed(SAVE_FAILED, 100000);
760 //showDialog(SAVING_DIALOG);
761 mHandler.sendEmptyMessageDelayed(SHOW_SAVING,50);
765 catch (Exception e) {
766 if ( e.getMessage() != null ) Log.println(Constants.LOGD,TAG,e.getMessage());
767 if (Constants.LOG_DEBUG)Log.println(Constants.LOGD,TAG,Log.getStackTraceString(e));
768 Toast.makeText(this, getString(R.string.ErrorSavingEvent), Toast.LENGTH_LONG).show();
769 return false;
772 return true;
775 @Override
776 public void onCheckedChanged(CompoundButton arg0, boolean arg1) {
777 this.updateLayout();
780 //Dialogs
781 protected Dialog onCreateDialog(int id) {
782 switch ( id ) {
783 case LOADING_DIALOG:
784 AlertDialog.Builder builder = new AlertDialog.Builder(this);
785 builder.setTitle("Loading...");
786 builder.setCancelable(false);
787 loadingDialog = builder.create();
788 return loadingDialog;
790 if ( todo == null ) return null;
792 // Any dialogs after this point depend on todo having been initialised
793 AcalDateTime start = todo.getStart();
794 AcalDateTime due = todo.getDue();
795 AcalDateTime completed = todo.getCompleted();
797 Boolean dateTypeIsDate = null;
798 if ( start == null ) {
799 start = new AcalDateTime().applyLocalTimeZone().addDays(1);
800 int newSecond = ((start.getDaySecond() / 3600) + 2) * 3600;
801 if ( newSecond > 86399 ) start.addDays(1);
802 start.setDaySecond(newSecond % 86400);
804 else {
805 dateTypeIsDate = start.isDate();
807 if ( due == null ) {
808 due = new AcalDateTime().applyLocalTimeZone().addDays(1);
809 int newSecond = start.getDaySecond() + 3600;
810 if ( newSecond > 86399 ) due.addDays(1);
811 due.setDaySecond(newSecond % 86400);
813 else if ( dateTypeIsDate == null ) {
814 dateTypeIsDate = due.isDate();
816 if ( completed == null ) {
817 completed = new AcalDateTime();
818 if ( start != null || due != null ) completed.setAsDate((start!=null?start.isDate():due.isDate()));
820 else if ( dateTypeIsDate == null ) {
821 dateTypeIsDate = completed.isDate();
823 if ( dateTypeIsDate == null ) dateTypeIsDate = true;
824 start.setAsDate(dateTypeIsDate);
825 due.setAsDate(dateTypeIsDate);
826 completed.setAsDate(dateTypeIsDate);
828 switch ( id ) {
829 case FROM_DIALOG:
830 return new DateTimeDialog( this, start, prefer24hourFormat, true, true,
831 new DateTimeSetListener() {
832 public void onDateTimeSet(AcalDateTime newDateTime) {
833 todo.setStart( newDateTime );
834 updateLayout();
838 case DUE_DIALOG:
839 return new DateTimeDialog( this, due, prefer24hourFormat, true, true,
840 new DateTimeSetListener() {
841 public void onDateTimeSet(AcalDateTime newDateTime) {
842 todo.setDue( newDateTime );
843 updateLayout();
847 case COMPLETED_DIALOG:
848 return new DateTimeDialog( this, completed, prefer24hourFormat, true, true,
849 new DateTimeSetListener() {
850 public void onDateTimeSet(AcalDateTime newDateTime) {
851 todo.setCompleted( newDateTime );
852 todo.setPercentComplete(100);
853 todo.setStatus(VTodo.Status.COMPLETED);
854 updateLayout();
859 case ADD_ALARM_DIALOG:
860 AlertDialog.Builder builder = new AlertDialog.Builder( this );
861 builder.setTitle( getString( R.string.ChooseAlarmTime ) );
862 builder.setItems( alarmRelativeTimeStrings, new DialogInterface.OnClickListener() {
863 public void onClick(DialogInterface dialog, int item) {
864 // translate item to equal alarmValue index
865 RelateWith relateWith = RelateWith.START;
866 AcalDateTime start = todo.getStart();
867 if ( start == null ) {
868 relateWith = (todo.getDue() == null ? RelateWith.ABSOLUTE : RelateWith.END);
869 if ( relateWith == RelateWith.ABSOLUTE ) {
870 start = new AcalDateTime();
871 start.addDays( 1 );
874 if ( item < 0 || item > alarmValues.length ) return;
875 if ( item == alarmValues.length ) {
876 customAlarmDialog();
878 else {
879 alarmList.add( new AcalAlarm( relateWith, todo.getDescription(), alarmValues[item],
880 ActionType.AUDIO, start, todo.getDue() ) );
881 todo.updateAlarmComponents( alarmList );
882 updateLayout();
885 } );
886 return builder.create();
887 case INSTANCES_TO_CHANGE_DIALOG:
888 builder = new AlertDialog.Builder( this );
889 builder.setTitle( getString( R.string.ChooseInstancesToChange ) );
890 builder.setItems( todoChangeRanges, new DialogInterface.OnClickListener() {
891 public void onClick(DialogInterface dialog, int item) {
892 switch ( item ) {
893 case 0:
894 action = ACTION_MODIFY_SINGLE;
895 saveChanges();
896 return;
897 case 1:
898 action = ACTION_MODIFY_ALL;
899 saveChanges();
900 return;
901 case 2:
902 action = ACTION_MODIFY_ALL_FUTURE;
903 saveChanges();
904 return;
907 } );
908 return builder.create();
909 case SET_REPEAT_RULE_DIALOG:
910 builder = new AlertDialog.Builder( this );
911 builder.setTitle( getString( R.string.ChooseRepeatFrequency ) );
912 builder.setItems( this.repeatRules, new DialogInterface.OnClickListener() {
913 public void onClick(DialogInterface dialog, int item) {
914 String newRule = "";
915 if ( item != 0 ) {
916 item--;
917 newRule = repeatRulesValues[item];
919 if ( isModifyAction() ) {
920 if ( TodoEdit.this.originalHasOccurrence
921 && !newRule.equals( TodoEdit.this.originalOccurence ) ) {
922 action = ACTION_MODIFY_ALL;
924 else if ( TodoEdit.this.originalHasOccurrence ) {
925 action = ACTION_MODIFY_SINGLE;
928 todo.setRepetition( newRule );
929 updateLayout();
932 } );
933 return builder.create();
934 default:
935 return null;
939 protected void customAlarmDialog() {
941 AlarmDialog.AlarmSetListener customAlarmListener = new AlarmDialog.AlarmSetListener() {
943 @Override
944 public void onAlarmSet(AcalAlarm alarmValue) {
945 alarmList.add( alarmValue );
946 todo.updateAlarmComponents(alarmList);
947 updateLayout();
952 AlarmDialog customAlarm = new AlarmDialog(this, customAlarmListener, null,
953 todo.getStart(), todo.getDue(), VComponent.VTODO);
954 customAlarm.show();
957 public View getAlarmItem(final AcalAlarm alarm, ViewGroup parent) {
958 LinearLayout rowLayout;
960 LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
961 TextView title = null; //, time = null, location = null;
962 rowLayout = (TableRow) inflater.inflate(R.layout.alarm_list_item, parent, false);
964 title = (TextView) rowLayout.findViewById(R.id.AlarmListItemTitle);
965 title.setText(alarm.toPrettyString());
967 ImageView cancel = (ImageView) rowLayout.findViewById(R.id.delete_button);
969 rowLayout.setTag(alarm);
970 cancel.setOnClickListener(new OnClickListener(){
971 public void onClick(View v) {
972 alarmList.remove(alarm);
973 updateLayout();
976 return rowLayout;
980 public boolean isModifyAction() {
981 return (action == ACTION_EDIT || (action > ACTION_CREATE && action <= ACTION_MODIFY_ALL));
985 @Override
986 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
987 if ( fromUser ) {
988 percentComplete = progress;
989 percentCompleteText.setText(Integer.toString(percentComplete)+"%");
990 if ( progress == 0 ) todo.setStatus(VTodo.Status.NEEDS_ACTION);
991 else if ( progress > 0 && progress < 100 ) todo.setStatus(VTodo.Status.IN_PROCESS);
992 else {
993 todo.setStatus(VTodo.Status.COMPLETED);
994 todo.setCompleted(new AcalDateTime() );
995 updateLayout();
1001 @Override
1002 public void onStartTrackingTouch(SeekBar seekBar) {
1006 @Override
1007 public void onStopTrackingTouch(SeekBar seekBar) {
1011 @Override
1012 public void resourceChanged(ResourceChangedEvent event) {
1013 // @todo Auto-generated method stub
1018 @Override
1019 public void resourceResponse(ResourceResponse response) {
1020 int msg = FAIL;
1021 Object result = response.result();
1022 if (result == null) {
1023 mHandler.sendMessage(mHandler.obtainMessage(msg));
1025 else if (result instanceof CalendarInstance) {
1026 if (response.wasSuccessful()) {
1027 setTodo( new VTodo((TodoInstance) response.result()) );
1028 msg = REFRESH;
1030 mHandler.sendMessage(mHandler.obtainMessage(msg));
1032 else if (result instanceof Long) {
1033 mHandler.sendMessage(mHandler.obtainMessage(SAVE_RESULT, result));