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";
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
[] {
128 new AcalDuration("-PT10M"),
129 new AcalDuration("-PT15M"),
130 new AcalDuration("-PT30M"),
131 new AcalDuration("-PT1H"),
132 new AcalDuration("-PT2H"),
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
;
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
) {
210 if ( loadingDialog
!= null ) {
211 loadingDialog
.dismiss();
212 loadingDialog
= null;
220 "The resource you are editing has been changed or deleted on the server.",
221 Toast
.LENGTH_LONG
).show();
224 if ( todo
== null ) showDialog(LOADING_DIALOG
);
229 Toast
.makeText(TodoEdit
.this, "Error loading data.", Toast
.LENGTH_LONG
).show();
232 else if ( isSaving
) {
234 if ( savingDialog
!= null ) savingDialog
236 Toast
.makeText( TodoEdit
.this, "Something went wrong trying to save data.", Toast
.LENGTH_LONG
).show();
237 setResult(Activity
.RESULT_CANCELED
, null);
242 if ( loadingDialog
!= null ) {
243 loadingDialog
.dismiss();
246 "Error loading event data.",
247 Toast
.LENGTH_LONG
).show();
255 showDialog(SAVING_DIALOG
);
261 mHandler
.removeMessages(SAVE_FAILED
);
263 if ( savingDialog
!= null ) savingDialog
.dismiss();
264 long res
= (Long
) msg
.obj
;
266 Intent ret
= new Intent();
267 Bundle b
= new Bundle();
269 setResult(RESULT_OK
, ret
);
270 saveSucceeded
= true;
276 Toast
.makeText(TodoEdit
.this, "Error saving event data.", Toast
.LENGTH_LONG
).show();
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());
289 Toast
.makeText( TodoEdit
.this, "Something went wrong trying to save data.", Toast
.LENGTH_LONG
).show();
290 setResult(Activity
.RESULT_CANCELED
, null);
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
311 this.collectionsArray
= new CollectionForArrayAdapter
[taskCollections
.length
];
314 for (ContentValues cv
: taskCollections
) {
315 collectionId
= cv
.getAsLong(DavCollections
._ID
);
316 collectionsArray
[count
++] = new CollectionForArrayAdapter(this,collectionId
);
320 resourceManager
= ResourceManager
.getInstance(this,this);
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();
335 public void onDestroy() {
336 if ( handlerContext
== this ) handlerContext
= null;
340 @SuppressWarnings("unchecked")
341 private void requestTodoResource() {
342 currentOperation
= ACTION_CREATE
;
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;
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
) {
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
;
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
{
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() {
436 sidebar
= (LinearLayout
)this.findViewById(R
.id
.TodoEditColourBar
);
437 sidebarBottom
= (LinearLayout
)this.findViewById(R
.id
.EventEditColourBarBottom
);
440 this.todoName
= (TextView
) this.findViewById(R
.id
.TodoName
);
441 todoName
.setSelectAllOnFocus(action
== ACTION_CREATE
);
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
);
451 spinnerCollection
.setOnItemSelectedListener( new OnItemSelectedListener() {
454 public void onItemSelected(AdapterView
<?
> arg0
, View arg1
, int arg2
, long arg3
) {
455 setSelectedCollection(collectionsArray
[arg2
].getCollectionId());
459 public void onNothingSelected(AdapterView
<?
> arg0
) {
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
);
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
) {
501 btnCancelChanges
.setOnClickListener(new View
.OnClickListener() {
502 public void onClick(View arg0
) {
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())
553 spinnerCollection
.setAdapter(collectionAdapter
);
554 if ( spinnerPosition
< collectionsArray
.length
)
555 //set the default according to value
556 spinnerCollection
.setSelection(spinnerPosition
);
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);
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
);
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
);
599 if ( start
== null && due
== null ) {
600 repeatsLayout
.setVisibility(View
.GONE
);
603 AcalDateTime relativeTo
= (start
== null ? due
: start
);
604 int dow
= relativeTo
.getWeekDay();;
605 int weekNum
= relativeTo
.getMonthWeek();
608 String dowLongString
= "";
609 String everyDowString
= "";
613 dowLongString
= getString(R
.string
.Monday
);
614 everyDowString
= getString(R
.string
.EveryMonday
);
618 dowLongString
= getString(R
.string
.Tuesday
);
619 everyDowString
= getString(R
.string
.EveryTuesday
);
623 dowLongString
= getString(R
.string
.Wednesday
);
624 everyDowString
= getString(R
.string
.EveryWednesday
);
628 dowLongString
= getString(R
.string
.Thursday
);
629 everyDowString
= getString(R
.string
.EveryThursday
);
633 dowLongString
= getString(R
.string
.Friday
);
634 everyDowString
= getString(R
.string
.EveryFriday
);
638 dowLongString
= getString(R
.string
.Saturday
);
639 everyDowString
= getString(R
.string
.EverySaturday
);
643 dowLongString
= getString(R
.string
.Sunday
);
644 everyDowString
= getString(R
.string
.EverySunday
);
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
),
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",
668 "FREQ=WEEKLY;BYDAY="+dowStr
,
669 "FREQ=MONTHLY;COUNT=60",
670 "FREQ=MONTHLY;COUNT=60;BYDAY="+weekNum
+dowStr
,
673 String repeatRuleString
= todo
.getRRule();
674 if (repeatRuleString
== null) repeatRuleString
= "";
675 AcalRepeatRule RRule
;
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);
700 private void setButtonDialog(Button myButton
, final int dialogIndicator
) {
701 myButton
.setOnClickListener(new View
.OnClickListener() {
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
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();
737 @SuppressWarnings("unchecked")
738 private boolean saveChanges() {
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();
776 public void onCheckedChanged(CompoundButton arg0
, boolean arg1
) {
781 protected Dialog
onCreateDialog(int id
) {
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);
805 dateTypeIsDate
= start
.isDate();
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
);
830 return new DateTimeDialog( this, start
, prefer24hourFormat
, true, true,
831 new DateTimeSetListener() {
832 public void onDateTimeSet(AcalDateTime newDateTime
) {
833 todo
.setStart( newDateTime
);
839 return new DateTimeDialog( this, due
, prefer24hourFormat
, true, true,
840 new DateTimeSetListener() {
841 public void onDateTimeSet(AcalDateTime newDateTime
) {
842 todo
.setDue( newDateTime
);
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
);
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();
874 if ( item
< 0 || item
> alarmValues
.length
) return;
875 if ( item
== alarmValues
.length
) {
879 alarmList
.add( new AcalAlarm( relateWith
, todo
.getDescription(), alarmValues
[item
],
880 ActionType
.AUDIO
, start
, todo
.getDue() ) );
881 todo
.updateAlarmComponents( alarmList
);
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
) {
894 action
= ACTION_MODIFY_SINGLE
;
898 action
= ACTION_MODIFY_ALL
;
902 action
= ACTION_MODIFY_ALL_FUTURE
;
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
) {
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
);
933 return builder
.create();
939 protected void customAlarmDialog() {
941 AlarmDialog
.AlarmSetListener customAlarmListener
= new AlarmDialog
.AlarmSetListener() {
944 public void onAlarmSet(AcalAlarm alarmValue
) {
945 alarmList
.add( alarmValue
);
946 todo
.updateAlarmComponents(alarmList
);
952 AlarmDialog customAlarm
= new AlarmDialog(this, customAlarmListener
, null,
953 todo
.getStart(), todo
.getDue(), VComponent
.VTODO
);
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
);
980 public boolean isModifyAction() {
981 return (action
== ACTION_EDIT
|| (action
> ACTION_CREATE
&& action
<= ACTION_MODIFY_ALL
));
986 public void onProgressChanged(SeekBar seekBar
, int progress
, boolean 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
);
993 todo
.setStatus(VTodo
.Status
.COMPLETED
);
994 todo
.setCompleted(new AcalDateTime() );
1002 public void onStartTrackingTouch(SeekBar seekBar
) {
1007 public void onStopTrackingTouch(SeekBar seekBar
) {
1012 public void resourceChanged(ResourceChangedEvent event
) {
1013 // @todo Auto-generated method stub
1019 public void resourceResponse(ResourceResponse response
) {
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()) );
1030 mHandler
.sendMessage(mHandler
.obtainMessage(msg
));
1032 else if (result
instanceof Long
) {
1033 mHandler
.sendMessage(mHandler
.obtainMessage(SAVE_RESULT
, result
));