1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.calendar.recurrencepicker; 18 19 import android.app.Activity; 20 import android.app.DialogFragment; 21 import android.content.Context; 22 import android.content.res.Configuration; 23 import android.content.res.Resources; 24 import android.os.Bundle; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 import android.text.Editable; 28 import android.text.TextUtils; 29 import android.text.TextWatcher; 30 import android.text.format.DateUtils; 31 import android.text.format.Time; 32 import android.util.Log; 33 import android.util.TimeFormatException; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.View.OnClickListener; 37 import android.view.ViewGroup; 38 import android.view.ViewGroup.LayoutParams; 39 import android.view.Window; 40 import android.widget.AdapterView; 41 import android.widget.AdapterView.OnItemSelectedListener; 42 import android.widget.ArrayAdapter; 43 import android.widget.Button; 44 import android.widget.CompoundButton; 45 import android.widget.CompoundButton.OnCheckedChangeListener; 46 import android.widget.EditText; 47 import android.widget.LinearLayout; 48 import android.widget.RadioButton; 49 import android.widget.RadioGroup; 50 import android.widget.Spinner; 51 import android.widget.Switch; 52 import android.widget.TableLayout; 53 import android.widget.TextView; 54 import android.widget.Toast; 55 import android.widget.ToggleButton; 56 57 import com.android.calendar.R; 58 import com.android.calendar.Utils; 59 import com.android.calendarcommon2.EventRecurrence; 60 import com.android.datetimepicker.date.DatePickerDialog; 61 62 import java.text.DateFormatSymbols; 63 import java.util.ArrayList; 64 import java.util.Arrays; 65 import java.util.Calendar; 66 67 public class RecurrencePickerDialog extends DialogFragment implements OnItemSelectedListener, 68 OnCheckedChangeListener, OnClickListener, 69 android.widget.RadioGroup.OnCheckedChangeListener, DatePickerDialog.OnDateSetListener { 70 71 private static final String TAG = "RecurrencePickerDialog"; 72 73 // in dp's 74 private static final int MIN_SCREEN_WIDTH_FOR_SINGLE_ROW_WEEK = 450; 75 76 // Update android:maxLength in EditText as needed 77 private static final int INTERVAL_MAX = 99; 78 private static final int INTERVAL_DEFAULT = 1; 79 // Update android:maxLength in EditText as needed 80 private static final int COUNT_MAX = 730; 81 private static final int COUNT_DEFAULT = 5; 82 83 // Special cases in monthlyByNthDayOfWeek 84 private static final int FIFTH_WEEK_IN_A_MONTH = 5; 85 private static final int LAST_NTH_DAY_OF_WEEK = -1; 86 87 private DatePickerDialog mDatePickerDialog; 88 89 private class RecurrenceModel implements Parcelable { 90 91 // Should match EventRecurrence.DAILY, etc 92 static final int FREQ_DAILY = 0; 93 static final int FREQ_WEEKLY = 1; 94 static final int FREQ_MONTHLY = 2; 95 static final int FREQ_YEARLY = 3; 96 97 static final int END_NEVER = 0; 98 static final int END_BY_DATE = 1; 99 static final int END_BY_COUNT = 2; 100 101 static final int MONTHLY_BY_DATE = 0; 102 static final int MONTHLY_BY_NTH_DAY_OF_WEEK = 1; 103 104 static final int STATE_NO_RECURRENCE = 0; 105 static final int STATE_RECURRENCE = 1; 106 107 int recurrenceState; 108 109 /** 110 * FREQ: Repeat pattern 111 * 112 * @see FREQ_DAILY 113 * @see FREQ_WEEKLY 114 * @see FREQ_MONTHLY 115 * @see FREQ_YEARLY 116 */ 117 int freq = FREQ_WEEKLY; 118 119 /** 120 * INTERVAL: Every n days/weeks/months/years. n >= 1 121 */ 122 int interval = INTERVAL_DEFAULT; 123 124 /** 125 * UNTIL and COUNT: How does the the event end? 126 * 127 * @see END_NEVER 128 * @see END_BY_DATE 129 * @see END_BY_COUNT 130 * @see untilDate 131 * @see untilCount 132 */ 133 int end; 134 135 /** 136 * UNTIL: Date of the last recurrence. Used when until == END_BY_DATE 137 */ 138 Time endDate; 139 140 /** 141 * COUNT: Times to repeat. Use when until == END_BY_COUNT 142 */ 143 int endCount = COUNT_DEFAULT; 144 145 /** 146 * BYDAY: Days of the week to be repeated. Sun = 0, Mon = 1, etc 147 */ 148 boolean[] weeklyByDayOfWeek = new boolean[7]; 149 150 /** 151 * BYDAY AND BYMONTHDAY: How to repeat monthly events? Same date of the 152 * month or Same nth day of week. 153 * 154 * @see MONTHLY_BY_DATE 155 * @see MONTHLY_BY_NTH_DAY_OF_WEEK 156 */ 157 int monthlyRepeat; 158 159 /** 160 * Day of the month to repeat. Used when monthlyRepeat == 161 * MONTHLY_BY_DATE 162 */ 163 int monthlyByMonthDay; 164 165 /** 166 * Day of the week to repeat. Used when monthlyRepeat == 167 * MONTHLY_BY_NTH_DAY_OF_WEEK 168 */ 169 int monthlyByDayOfWeek; 170 171 /** 172 * Nth day of the week to repeat. Used when monthlyRepeat == 173 * MONTHLY_BY_NTH_DAY_OF_WEEK 0=undefined, -1=Last, 1=1st, 2=2nd, ..., 5=5th 174 * 175 * We support 5th, just to handle backwards capabilities with old bug, but it 176 * gets converted to -1 once edited. 177 */ 178 int monthlyByNthDayOfWeek; 179 180 /* 181 * (generated method) 182 */ 183 @Override 184 public String toString() { 185 return "Model [freq=" + freq + ", interval=" + interval + ", end=" + end + ", endDate=" 186 + endDate + ", endCount=" + endCount + ", weeklyByDayOfWeek=" 187 + Arrays.toString(weeklyByDayOfWeek) + ", monthlyRepeat=" + monthlyRepeat 188 + ", monthlyByMonthDay=" + monthlyByMonthDay + ", monthlyByDayOfWeek=" 189 + monthlyByDayOfWeek + ", monthlyByNthDayOfWeek=" + monthlyByNthDayOfWeek + "]"; 190 } 191 192 @Override 193 public int describeContents() { 194 return 0; 195 } 196 197 public RecurrenceModel() { 198 } 199 200 @Override 201 public void writeToParcel(Parcel dest, int flags) { 202 dest.writeInt(freq); 203 dest.writeInt(interval); 204 dest.writeInt(end); 205 dest.writeInt(endDate.year); 206 dest.writeInt(endDate.month); 207 dest.writeInt(endDate.monthDay); 208 dest.writeInt(endCount); 209 dest.writeBooleanArray(weeklyByDayOfWeek); 210 dest.writeInt(monthlyRepeat); 211 dest.writeInt(monthlyByMonthDay); 212 dest.writeInt(monthlyByDayOfWeek); 213 dest.writeInt(monthlyByNthDayOfWeek); 214 dest.writeInt(recurrenceState); 215 } 216 } 217 218 class minMaxTextWatcher implements TextWatcher { 219 private int mMin; 220 private int mMax; 221 private int mDefault; 222 223 public minMaxTextWatcher(int min, int defaultInt, int max) { 224 mMin = min; 225 mMax = max; 226 mDefault = defaultInt; 227 } 228 229 @Override 230 public void afterTextChanged(Editable s) { 231 232 boolean updated = false; 233 int value; 234 try { 235 value = Integer.parseInt(s.toString()); 236 } catch (NumberFormatException e) { 237 value = mDefault; 238 } 239 240 if (value < mMin) { 241 value = mMin; 242 updated = true; 243 } else if (value > mMax) { 244 updated = true; 245 value = mMax; 246 } 247 248 // Update UI 249 if (updated) { 250 s.clear(); 251 s.append(Integer.toString(value)); 252 } 253 254 updateDoneButtonState(); 255 onChange(value); 256 } 257 258 /** Override to be called after each key stroke */ 259 void onChange(int value) { 260 } 261 262 @Override 263 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 264 } 265 266 @Override 267 public void onTextChanged(CharSequence s, int start, int before, int count) { 268 } 269 } 270 271 private Resources mResources; 272 private EventRecurrence mRecurrence = new EventRecurrence(); 273 private Time mTime = new Time(); // TODO timezone? 274 private RecurrenceModel mModel = new RecurrenceModel(); 275 private Toast mToast; 276 277 private final int[] TIME_DAY_TO_CALENDAR_DAY = new int[] { 278 Calendar.SUNDAY, 279 Calendar.MONDAY, 280 Calendar.TUESDAY, 281 Calendar.WEDNESDAY, 282 Calendar.THURSDAY, 283 Calendar.FRIDAY, 284 Calendar.SATURDAY, 285 }; 286 287 // Call mStringBuilder.setLength(0) before formatting any string or else the 288 // formatted text will accumulate. 289 // private final StringBuilder mStringBuilder = new StringBuilder(); 290 // private Formatter mFormatter = new Formatter(mStringBuilder); 291 292 private View mView; 293 294 private Spinner mFreqSpinner; 295 private static final int[] mFreqModelToEventRecurrence = { 296 EventRecurrence.DAILY, 297 EventRecurrence.WEEKLY, 298 EventRecurrence.MONTHLY, 299 EventRecurrence.YEARLY 300 }; 301 302 public static final String BUNDLE_START_TIME_MILLIS = "bundle_event_start_time"; 303 public static final String BUNDLE_TIME_ZONE = "bundle_event_time_zone"; 304 public static final String BUNDLE_RRULE = "bundle_event_rrule"; 305 306 private static final String BUNDLE_MODEL = "bundle_model"; 307 private static final String BUNDLE_END_COUNT_HAS_FOCUS = "bundle_end_count_has_focus"; 308 309 private static final String FRAG_TAG_DATE_PICKER = "tag_date_picker_frag"; 310 311 private Switch mRepeatSwitch; 312 313 private EditText mInterval; 314 private TextView mIntervalPreText; 315 private TextView mIntervalPostText; 316 317 private int mIntervalResId = -1; 318 319 private Spinner mEndSpinner; 320 private TextView mEndDateTextView; 321 private EditText mEndCount; 322 private TextView mPostEndCount; 323 private boolean mHidePostEndCount; 324 325 private ArrayList<CharSequence> mEndSpinnerArray = new ArrayList<CharSequence>(3); 326 private EndSpinnerAdapter mEndSpinnerAdapter; 327 private String mEndNeverStr; 328 private String mEndDateLabel; 329 private String mEndCountLabel; 330 331 /** Hold toggle buttons in the order per user's first day of week preference */ 332 private LinearLayout mWeekGroup; 333 private LinearLayout mWeekGroup2; 334 // Sun = 0 335 private ToggleButton[] mWeekByDayButtons = new ToggleButton[7]; 336 /** A double array of Strings to hold the 7x5 list of possible strings of the form: 337 * "on every [Nth] [DAY_OF_WEEK]", e.g. "on every second Monday", 338 * where [Nth] can be [first, second, third, fourth, last] */ 339 private String[][] mMonthRepeatByDayOfWeekStrs; 340 341 private LinearLayout mMonthGroup; 342 private RadioGroup mMonthRepeatByRadioGroup; 343 private RadioButton mRepeatMonthlyByNthDayOfWeek; 344 private RadioButton mRepeatMonthlyByNthDayOfMonth; 345 private String mMonthRepeatByDayOfWeekStr; 346 347 private Button mDone; 348 349 private OnRecurrenceSetListener mRecurrenceSetListener; 350 351 public RecurrencePickerDialog() { 352 } 353 354 static public boolean isSupportedMonthlyByNthDayOfWeek(int num) { 355 // We only support monthlyByNthDayOfWeek when it is greater then 0 but less then 5. 356 // Or if -1 when it is the last monthly day of the week. 357 return (num > 0 && num <= FIFTH_WEEK_IN_A_MONTH) || num == LAST_NTH_DAY_OF_WEEK; 358 } 359 360 static public boolean canHandleRecurrenceRule(EventRecurrence er) { 361 switch (er.freq) { 362 case EventRecurrence.DAILY: 363 case EventRecurrence.MONTHLY: 364 case EventRecurrence.YEARLY: 365 case EventRecurrence.WEEKLY: 366 break; 367 default: 368 return false; 369 } 370 371 if (er.count > 0 && !TextUtils.isEmpty(er.until)) { 372 return false; 373 } 374 375 // Weekly: For "repeat by day of week", the day of week to repeat is in 376 // er.byday[] 377 378 /* 379 * Monthly: For "repeat by nth day of week" the day of week to repeat is 380 * in er.byday[] and the "nth" is stored in er.bydayNum[]. Currently we 381 * can handle only one and only in monthly 382 */ 383 int numOfByDayNum = 0; 384 for (int i = 0; i < er.bydayCount; i++) { 385 if (isSupportedMonthlyByNthDayOfWeek(er.bydayNum[i])) { 386 ++numOfByDayNum; 387 } 388 } 389 390 if (numOfByDayNum > 1) { 391 return false; 392 } 393 394 if (numOfByDayNum > 0 && er.freq != EventRecurrence.MONTHLY) { 395 return false; 396 } 397 398 // The UI only handle repeat by one day of month i.e. not 9th and 10th 399 // of every month 400 if (er.bymonthdayCount > 1) { 401 return false; 402 } 403 404 if (er.freq == EventRecurrence.MONTHLY) { 405 if (er.bydayCount > 1) { 406 return false; 407 } 408 if (er.bydayCount > 0 && er.bymonthdayCount > 0) { 409 return false; 410 } 411 } 412 413 return true; 414 } 415 416 // TODO don't lose data when getting data that our UI can't handle 417 static private void copyEventRecurrenceToModel(final EventRecurrence er, 418 RecurrenceModel model) { 419 // Freq: 420 switch (er.freq) { 421 case EventRecurrence.DAILY: 422 model.freq = RecurrenceModel.FREQ_DAILY; 423 break; 424 case EventRecurrence.MONTHLY: 425 model.freq = RecurrenceModel.FREQ_MONTHLY; 426 break; 427 case EventRecurrence.YEARLY: 428 model.freq = RecurrenceModel.FREQ_YEARLY; 429 break; 430 case EventRecurrence.WEEKLY: 431 model.freq = RecurrenceModel.FREQ_WEEKLY; 432 break; 433 default: 434 throw new IllegalStateException("freq=" + er.freq); 435 } 436 437 // Interval: 438 if (er.interval > 0) { 439 model.interval = er.interval; 440 } 441 442 // End: 443 // End by count: 444 model.endCount = er.count; 445 if (model.endCount > 0) { 446 model.end = RecurrenceModel.END_BY_COUNT; 447 } 448 449 // End by date: 450 if (!TextUtils.isEmpty(er.until)) { 451 if (model.endDate == null) { 452 model.endDate = new Time(); 453 } 454 455 try { 456 model.endDate.parse(er.until); 457 } catch (TimeFormatException e) { 458 model.endDate = null; 459 } 460 461 // LIMITATION: The UI can only handle END_BY_DATE or END_BY_COUNT 462 if (model.end == RecurrenceModel.END_BY_COUNT && model.endDate != null) { 463 throw new IllegalStateException("freq=" + er.freq); 464 } 465 466 model.end = RecurrenceModel.END_BY_DATE; 467 } 468 469 // Weekly: repeat by day of week or Monthly: repeat by nth day of week 470 // in the month 471 Arrays.fill(model.weeklyByDayOfWeek, false); 472 if (er.bydayCount > 0) { 473 int count = 0; 474 for (int i = 0; i < er.bydayCount; i++) { 475 int dayOfWeek = EventRecurrence.day2TimeDay(er.byday[i]); 476 model.weeklyByDayOfWeek[dayOfWeek] = true; 477 478 if (model.freq == RecurrenceModel.FREQ_MONTHLY && 479 isSupportedMonthlyByNthDayOfWeek(er.bydayNum[i])) { 480 // LIMITATION: Can handle only (one) weekDayNum in nth or last and only 481 // when 482 // monthly 483 model.monthlyByDayOfWeek = dayOfWeek; 484 model.monthlyByNthDayOfWeek = er.bydayNum[i]; 485 model.monthlyRepeat = RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK; 486 count++; 487 } 488 } 489 490 if (model.freq == RecurrenceModel.FREQ_MONTHLY) { 491 if (er.bydayCount != 1) { 492 // Can't handle 1st Monday and 2nd Wed 493 throw new IllegalStateException("Can handle only 1 byDayOfWeek in monthly"); 494 } 495 if (count != 1) { 496 throw new IllegalStateException( 497 "Didn't specify which nth day of week to repeat for a monthly"); 498 } 499 } 500 } 501 502 // Monthly by day of month 503 if (model.freq == RecurrenceModel.FREQ_MONTHLY) { 504 if (er.bymonthdayCount == 1) { 505 if (model.monthlyRepeat == RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK) { 506 throw new IllegalStateException( 507 "Can handle only by monthday or by nth day of week, not both"); 508 } 509 model.monthlyByMonthDay = er.bymonthday[0]; 510 model.monthlyRepeat = RecurrenceModel.MONTHLY_BY_DATE; 511 } else if (er.bymonthCount > 1) { 512 // LIMITATION: Can handle only one month day 513 throw new IllegalStateException("Can handle only one bymonthday"); 514 } 515 } 516 } 517 518 static private void copyModelToEventRecurrence(final RecurrenceModel model, 519 EventRecurrence er) { 520 if (model.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) { 521 throw new IllegalStateException("There's no recurrence"); 522 } 523 524 // Freq 525 er.freq = mFreqModelToEventRecurrence[model.freq]; 526 527 // Interval 528 if (model.interval <= 1) { 529 er.interval = 0; 530 } else { 531 er.interval = model.interval; 532 } 533 534 // End 535 switch (model.end) { 536 case RecurrenceModel.END_BY_DATE: 537 if (model.endDate != null) { 538 model.endDate.switchTimezone(Time.TIMEZONE_UTC); 539 model.endDate.normalize(false); 540 er.until = model.endDate.format2445(); 541 er.count = 0; 542 } else { 543 throw new IllegalStateException("end = END_BY_DATE but endDate is null"); 544 } 545 break; 546 case RecurrenceModel.END_BY_COUNT: 547 er.count = model.endCount; 548 er.until = null; 549 if (er.count <= 0) { 550 throw new IllegalStateException("count is " + er.count); 551 } 552 break; 553 default: 554 er.count = 0; 555 er.until = null; 556 break; 557 } 558 559 // Weekly && monthly repeat patterns 560 er.bydayCount = 0; 561 er.bymonthdayCount = 0; 562 563 switch (model.freq) { 564 case RecurrenceModel.FREQ_MONTHLY: 565 if (model.monthlyRepeat == RecurrenceModel.MONTHLY_BY_DATE) { 566 if (model.monthlyByMonthDay > 0) { 567 if (er.bymonthday == null || er.bymonthdayCount < 1) { 568 er.bymonthday = new int[1]; 569 } 570 er.bymonthday[0] = model.monthlyByMonthDay; 571 er.bymonthdayCount = 1; 572 } 573 } else if (model.monthlyRepeat == RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK) { 574 if (!isSupportedMonthlyByNthDayOfWeek(model.monthlyByNthDayOfWeek)) { 575 throw new IllegalStateException("month repeat by nth week but n is " 576 + model.monthlyByNthDayOfWeek); 577 } 578 int count = 1; 579 if (er.bydayCount < count || er.byday == null || er.bydayNum == null) { 580 er.byday = new int[count]; 581 er.bydayNum = new int[count]; 582 } 583 er.bydayCount = count; 584 er.byday[0] = EventRecurrence.timeDay2Day(model.monthlyByDayOfWeek); 585 er.bydayNum[0] = model.monthlyByNthDayOfWeek; 586 } 587 break; 588 case RecurrenceModel.FREQ_WEEKLY: 589 int count = 0; 590 for (int i = 0; i < 7; i++) { 591 if (model.weeklyByDayOfWeek[i]) { 592 count++; 593 } 594 } 595 596 if (er.bydayCount < count || er.byday == null || er.bydayNum == null) { 597 er.byday = new int[count]; 598 er.bydayNum = new int[count]; 599 } 600 er.bydayCount = count; 601 602 for (int i = 6; i >= 0; i--) { 603 if (model.weeklyByDayOfWeek[i]) { 604 er.bydayNum[--count] = 0; 605 er.byday[count] = EventRecurrence.timeDay2Day(i); 606 } 607 } 608 break; 609 } 610 611 if (!canHandleRecurrenceRule(er)) { 612 throw new IllegalStateException("UI generated recurrence that it can't handle. ER:" 613 + er.toString() + " Model: " + model.toString()); 614 } 615 } 616 617 @Override 618 public View onCreateView(LayoutInflater inflater, ViewGroup container, 619 Bundle savedInstanceState) { 620 mRecurrence.wkst = EventRecurrence.timeDay2Day(Utils.getFirstDayOfWeek(getActivity())); 621 622 getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE); 623 624 boolean endCountHasFocus = false; 625 if (savedInstanceState != null) { 626 RecurrenceModel m = (RecurrenceModel) savedInstanceState.get(BUNDLE_MODEL); 627 if (m != null) { 628 mModel = m; 629 } 630 endCountHasFocus = savedInstanceState.getBoolean(BUNDLE_END_COUNT_HAS_FOCUS); 631 } else { 632 Bundle b = getArguments(); 633 if (b != null) { 634 mTime.set(b.getLong(BUNDLE_START_TIME_MILLIS)); 635 636 String tz = b.getString(BUNDLE_TIME_ZONE); 637 if (!TextUtils.isEmpty(tz)) { 638 mTime.timezone = tz; 639 } 640 mTime.normalize(false); 641 642 // Time days of week: Sun=0, Mon=1, etc 643 mModel.weeklyByDayOfWeek[mTime.weekDay] = true; 644 String rrule = b.getString(BUNDLE_RRULE); 645 if (!TextUtils.isEmpty(rrule)) { 646 mModel.recurrenceState = RecurrenceModel.STATE_RECURRENCE; 647 mRecurrence.parse(rrule); 648 copyEventRecurrenceToModel(mRecurrence, mModel); 649 // Leave today's day of week as checked by default in weekly view. 650 if (mRecurrence.bydayCount == 0) { 651 mModel.weeklyByDayOfWeek[mTime.weekDay] = true; 652 } 653 } 654 655 } else { 656 mTime.setToNow(); 657 } 658 } 659 660 mResources = getResources(); 661 mView = inflater.inflate(R.layout.recurrencepicker, container, true); 662 663 final Activity activity = getActivity(); 664 final Configuration config = activity.getResources().getConfiguration(); 665 666 mRepeatSwitch = (Switch) mView.findViewById(R.id.repeat_switch); 667 mRepeatSwitch.setChecked(mModel.recurrenceState == RecurrenceModel.STATE_RECURRENCE); 668 mRepeatSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() { 669 670 @Override 671 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 672 mModel.recurrenceState = isChecked ? RecurrenceModel.STATE_RECURRENCE 673 : RecurrenceModel.STATE_NO_RECURRENCE; 674 togglePickerOptions(); 675 } 676 }); 677 678 mFreqSpinner = (Spinner) mView.findViewById(R.id.freqSpinner); 679 mFreqSpinner.setOnItemSelectedListener(this); 680 ArrayAdapter<CharSequence> freqAdapter = ArrayAdapter.createFromResource(getActivity(), 681 R.array.recurrence_freq, R.layout.recurrencepicker_freq_item); 682 freqAdapter.setDropDownViewResource(R.layout.recurrencepicker_freq_item); 683 mFreqSpinner.setAdapter(freqAdapter); 684 685 mInterval = (EditText) mView.findViewById(R.id.interval); 686 mInterval.addTextChangedListener(new minMaxTextWatcher(1, INTERVAL_DEFAULT, INTERVAL_MAX) { 687 @Override 688 void onChange(int v) { 689 if (mIntervalResId != -1 && mInterval.getText().toString().length() > 0) { 690 mModel.interval = v; 691 updateIntervalText(); 692 mInterval.requestLayout(); 693 } 694 } 695 }); 696 mIntervalPreText = (TextView) mView.findViewById(R.id.intervalPreText); 697 mIntervalPostText = (TextView) mView.findViewById(R.id.intervalPostText); 698 699 mEndNeverStr = mResources.getString(R.string.recurrence_end_continously); 700 mEndDateLabel = mResources.getString(R.string.recurrence_end_date_label); 701 mEndCountLabel = mResources.getString(R.string.recurrence_end_count_label); 702 703 mEndSpinnerArray.add(mEndNeverStr); 704 mEndSpinnerArray.add(mEndDateLabel); 705 mEndSpinnerArray.add(mEndCountLabel); 706 mEndSpinner = (Spinner) mView.findViewById(R.id.endSpinner); 707 mEndSpinner.setOnItemSelectedListener(this); 708 mEndSpinnerAdapter = new EndSpinnerAdapter(getActivity(), mEndSpinnerArray, 709 R.layout.recurrencepicker_freq_item, R.layout.recurrencepicker_end_text); 710 mEndSpinnerAdapter.setDropDownViewResource(R.layout.recurrencepicker_freq_item); 711 mEndSpinner.setAdapter(mEndSpinnerAdapter); 712 713 mEndCount = (EditText) mView.findViewById(R.id.endCount); 714 mEndCount.addTextChangedListener(new minMaxTextWatcher(1, COUNT_DEFAULT, COUNT_MAX) { 715 @Override 716 void onChange(int v) { 717 if (mModel.endCount != v) { 718 mModel.endCount = v; 719 updateEndCountText(); 720 mEndCount.requestLayout(); 721 } 722 } 723 }); 724 mPostEndCount = (TextView) mView.findViewById(R.id.postEndCount); 725 726 mEndDateTextView = (TextView) mView.findViewById(R.id.endDate); 727 mEndDateTextView.setOnClickListener(this); 728 if (mModel.endDate == null) { 729 mModel.endDate = new Time(mTime); 730 switch (mModel.freq) { 731 case RecurrenceModel.FREQ_DAILY: 732 case RecurrenceModel.FREQ_WEEKLY: 733 mModel.endDate.month += 1; 734 break; 735 case RecurrenceModel.FREQ_MONTHLY: 736 mModel.endDate.month += 3; 737 break; 738 case RecurrenceModel.FREQ_YEARLY: 739 mModel.endDate.year += 3; 740 break; 741 } 742 mModel.endDate.normalize(false); 743 } 744 745 mWeekGroup = (LinearLayout) mView.findViewById(R.id.weekGroup); 746 mWeekGroup2 = (LinearLayout) mView.findViewById(R.id.weekGroup2); 747 748 // In Calendar.java day of week order e.g Sun = 1 ... Sat = 7 749 String[] dayOfWeekString = new DateFormatSymbols().getWeekdays(); 750 751 mMonthRepeatByDayOfWeekStrs = new String[7][]; 752 // from Time.SUNDAY as 0 through Time.SATURDAY as 6 753 mMonthRepeatByDayOfWeekStrs[0] = mResources.getStringArray(R.array.repeat_by_nth_sun); 754 mMonthRepeatByDayOfWeekStrs[1] = mResources.getStringArray(R.array.repeat_by_nth_mon); 755 mMonthRepeatByDayOfWeekStrs[2] = mResources.getStringArray(R.array.repeat_by_nth_tues); 756 mMonthRepeatByDayOfWeekStrs[3] = mResources.getStringArray(R.array.repeat_by_nth_wed); 757 mMonthRepeatByDayOfWeekStrs[4] = mResources.getStringArray(R.array.repeat_by_nth_thurs); 758 mMonthRepeatByDayOfWeekStrs[5] = mResources.getStringArray(R.array.repeat_by_nth_fri); 759 mMonthRepeatByDayOfWeekStrs[6] = mResources.getStringArray(R.array.repeat_by_nth_sat); 760 761 // In Time.java day of week order e.g. Sun = 0 762 int idx = Utils.getFirstDayOfWeek(getActivity()); 763 764 // In Calendar.java day of week order e.g Sun = 1 ... Sat = 7 765 dayOfWeekString = new DateFormatSymbols().getShortWeekdays(); 766 767 int numOfButtonsInRow1; 768 int numOfButtonsInRow2; 769 770 if (mResources.getConfiguration().screenWidthDp > MIN_SCREEN_WIDTH_FOR_SINGLE_ROW_WEEK) { 771 numOfButtonsInRow1 = 7; 772 numOfButtonsInRow2 = 0; 773 mWeekGroup2.setVisibility(View.GONE); 774 mWeekGroup2.getChildAt(3).setVisibility(View.GONE); 775 } else { 776 numOfButtonsInRow1 = 4; 777 numOfButtonsInRow2 = 3; 778 779 mWeekGroup2.setVisibility(View.VISIBLE); 780 // Set rightmost button on the second row invisible so it takes up 781 // space and everything centers properly 782 mWeekGroup2.getChildAt(3).setVisibility(View.INVISIBLE); 783 } 784 785 /* First row */ 786 for (int i = 0; i < 7; i++) { 787 if (i >= numOfButtonsInRow1) { 788 mWeekGroup.getChildAt(i).setVisibility(View.GONE); 789 continue; 790 } 791 792 mWeekByDayButtons[idx] = (ToggleButton) mWeekGroup.getChildAt(i); 793 mWeekByDayButtons[idx].setTextOff(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]); 794 mWeekByDayButtons[idx].setTextOn(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]); 795 mWeekByDayButtons[idx].setOnCheckedChangeListener(this); 796 797 if (++idx >= 7) { 798 idx = 0; 799 } 800 } 801 802 /* 2nd Row */ 803 for (int i = 0; i < 3; i++) { 804 if (i >= numOfButtonsInRow2) { 805 mWeekGroup2.getChildAt(i).setVisibility(View.GONE); 806 continue; 807 } 808 mWeekByDayButtons[idx] = (ToggleButton) mWeekGroup2.getChildAt(i); 809 mWeekByDayButtons[idx].setTextOff(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]); 810 mWeekByDayButtons[idx].setTextOn(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]); 811 mWeekByDayButtons[idx].setOnCheckedChangeListener(this); 812 813 if (++idx >= 7) { 814 idx = 0; 815 } 816 } 817 818 mMonthGroup = (LinearLayout) mView.findViewById(R.id.monthGroup); 819 mMonthRepeatByRadioGroup = (RadioGroup) mView.findViewById(R.id.monthGroup); 820 mMonthRepeatByRadioGroup.setOnCheckedChangeListener(this); 821 mRepeatMonthlyByNthDayOfWeek = (RadioButton) mView 822 .findViewById(R.id.repeatMonthlyByNthDayOfTheWeek); 823 mRepeatMonthlyByNthDayOfMonth = (RadioButton) mView 824 .findViewById(R.id.repeatMonthlyByNthDayOfMonth); 825 826 mDone = (Button) mView.findViewById(R.id.done); 827 mDone.setOnClickListener(this); 828 829 togglePickerOptions(); 830 updateDialog(); 831 if (endCountHasFocus) { 832 mEndCount.requestFocus(); 833 } 834 return mView; 835 } 836 837 private void togglePickerOptions() { 838 if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) { 839 mFreqSpinner.setEnabled(false); 840 mEndSpinner.setEnabled(false); 841 mIntervalPreText.setEnabled(false); 842 mInterval.setEnabled(false); 843 mIntervalPostText.setEnabled(false); 844 mMonthRepeatByRadioGroup.setEnabled(false); 845 mEndCount.setEnabled(false); 846 mPostEndCount.setEnabled(false); 847 mEndDateTextView.setEnabled(false); 848 mRepeatMonthlyByNthDayOfWeek.setEnabled(false); 849 mRepeatMonthlyByNthDayOfMonth.setEnabled(false); 850 for (Button button : mWeekByDayButtons) { 851 button.setEnabled(false); 852 } 853 } else { 854 mView.findViewById(R.id.options).setEnabled(true); 855 mFreqSpinner.setEnabled(true); 856 mEndSpinner.setEnabled(true); 857 mIntervalPreText.setEnabled(true); 858 mInterval.setEnabled(true); 859 mIntervalPostText.setEnabled(true); 860 mMonthRepeatByRadioGroup.setEnabled(true); 861 mEndCount.setEnabled(true); 862 mPostEndCount.setEnabled(true); 863 mEndDateTextView.setEnabled(true); 864 mRepeatMonthlyByNthDayOfWeek.setEnabled(true); 865 mRepeatMonthlyByNthDayOfMonth.setEnabled(true); 866 for (Button button : mWeekByDayButtons) { 867 button.setEnabled(true); 868 } 869 } 870 updateDoneButtonState(); 871 } 872 873 private void updateDoneButtonState() { 874 if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) { 875 mDone.setEnabled(true); 876 return; 877 } 878 879 if (mInterval.getText().toString().length() == 0) { 880 mDone.setEnabled(false); 881 return; 882 } 883 884 if (mEndCount.getVisibility() == View.VISIBLE && 885 mEndCount.getText().toString().length() == 0) { 886 mDone.setEnabled(false); 887 return; 888 } 889 890 if (mModel.freq == RecurrenceModel.FREQ_WEEKLY) { 891 for (CompoundButton b : mWeekByDayButtons) { 892 if (b.isChecked()) { 893 mDone.setEnabled(true); 894 return; 895 } 896 } 897 mDone.setEnabled(false); 898 return; 899 } 900 901 mDone.setEnabled(true); 902 } 903 904 @Override 905 public void onSaveInstanceState(Bundle outState) { 906 super.onSaveInstanceState(outState); 907 outState.putParcelable(BUNDLE_MODEL, mModel); 908 if (mEndCount.hasFocus()) { 909 outState.putBoolean(BUNDLE_END_COUNT_HAS_FOCUS, true); 910 } 911 } 912 913 public void updateDialog() { 914 // Interval 915 // Checking before setting because this causes infinite recursion 916 // in afterTextWatcher 917 final String intervalStr = Integer.toString(mModel.interval); 918 if (!intervalStr.equals(mInterval.getText().toString())) { 919 mInterval.setText(intervalStr); 920 } 921 922 mFreqSpinner.setSelection(mModel.freq); 923 mWeekGroup.setVisibility(mModel.freq == RecurrenceModel.FREQ_WEEKLY ? View.VISIBLE : View.GONE); 924 mWeekGroup2.setVisibility(mModel.freq == RecurrenceModel.FREQ_WEEKLY ? View.VISIBLE : View.GONE); 925 mMonthGroup.setVisibility(mModel.freq == RecurrenceModel.FREQ_MONTHLY ? View.VISIBLE : View.GONE); 926 927 switch (mModel.freq) { 928 case RecurrenceModel.FREQ_DAILY: 929 mIntervalResId = R.plurals.recurrence_interval_daily; 930 break; 931 932 case RecurrenceModel.FREQ_WEEKLY: 933 mIntervalResId = R.plurals.recurrence_interval_weekly; 934 for (int i = 0; i < 7; i++) { 935 mWeekByDayButtons[i].setChecked(mModel.weeklyByDayOfWeek[i]); 936 } 937 break; 938 939 case RecurrenceModel.FREQ_MONTHLY: 940 mIntervalResId = R.plurals.recurrence_interval_monthly; 941 942 if (mModel.monthlyRepeat == RecurrenceModel.MONTHLY_BY_DATE) { 943 mMonthRepeatByRadioGroup.check(R.id.repeatMonthlyByNthDayOfMonth); 944 } else if (mModel.monthlyRepeat == RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK) { 945 mMonthRepeatByRadioGroup.check(R.id.repeatMonthlyByNthDayOfTheWeek); 946 } 947 948 if (mMonthRepeatByDayOfWeekStr == null) { 949 if (mModel.monthlyByNthDayOfWeek == 0) { 950 mModel.monthlyByNthDayOfWeek = (mTime.monthDay + 6) / 7; 951 // Since not all months have 5 weeks, we convert 5th NthDayOfWeek to 952 // -1 for last monthly day of the week 953 if (mModel.monthlyByNthDayOfWeek >= FIFTH_WEEK_IN_A_MONTH) { 954 mModel.monthlyByNthDayOfWeek = LAST_NTH_DAY_OF_WEEK; 955 } 956 mModel.monthlyByDayOfWeek = mTime.weekDay; 957 } 958 959 String[] monthlyByNthDayOfWeekStrs = 960 mMonthRepeatByDayOfWeekStrs[mModel.monthlyByDayOfWeek]; 961 962 // TODO(psliwowski): Find a better way handle -1 indexes 963 int msgIndex = mModel.monthlyByNthDayOfWeek < 0 ? FIFTH_WEEK_IN_A_MONTH : 964 mModel.monthlyByNthDayOfWeek; 965 mMonthRepeatByDayOfWeekStr = 966 monthlyByNthDayOfWeekStrs[msgIndex - 1]; 967 mRepeatMonthlyByNthDayOfWeek.setText(mMonthRepeatByDayOfWeekStr); 968 } 969 break; 970 971 case RecurrenceModel.FREQ_YEARLY: 972 mIntervalResId = R.plurals.recurrence_interval_yearly; 973 break; 974 } 975 updateIntervalText(); 976 updateDoneButtonState(); 977 978 mEndSpinner.setSelection(mModel.end); 979 if (mModel.end == RecurrenceModel.END_BY_DATE) { 980 final String dateStr = DateUtils.formatDateTime(getActivity(), 981 mModel.endDate.toMillis(false), DateUtils.FORMAT_NUMERIC_DATE); 982 mEndDateTextView.setText(dateStr); 983 } else { 984 if (mModel.end == RecurrenceModel.END_BY_COUNT) { 985 // Checking before setting because this causes infinite 986 // recursion 987 // in afterTextWatcher 988 final String countStr = Integer.toString(mModel.endCount); 989 if (!countStr.equals(mEndCount.getText().toString())) { 990 mEndCount.setText(countStr); 991 } 992 } 993 } 994 } 995 996 /** 997 * @param endDateString 998 */ 999 private void setEndSpinnerEndDateStr(final String endDateString) { 1000 mEndSpinnerArray.set(1, endDateString); 1001 mEndSpinnerAdapter.notifyDataSetChanged(); 1002 } 1003 1004 private void doToast() { 1005 Log.e(TAG, "Model = " + mModel.toString()); 1006 String rrule; 1007 if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) { 1008 rrule = "Not repeating"; 1009 } else { 1010 copyModelToEventRecurrence(mModel, mRecurrence); 1011 rrule = mRecurrence.toString(); 1012 } 1013 1014 if (mToast != null) { 1015 mToast.cancel(); 1016 } 1017 mToast = Toast.makeText(getActivity(), rrule, 1018 Toast.LENGTH_LONG); 1019 mToast.show(); 1020 } 1021 1022 // TODO Test and update for Right-to-Left 1023 private void updateIntervalText() { 1024 if (mIntervalResId == -1) { 1025 return; 1026 } 1027 1028 final String INTERVAL_COUNT_MARKER = "%d"; 1029 String intervalString = mResources.getQuantityString(mIntervalResId, mModel.interval); 1030 int markerStart = intervalString.indexOf(INTERVAL_COUNT_MARKER); 1031 1032 if (markerStart != -1) { 1033 int postTextStart = markerStart + INTERVAL_COUNT_MARKER.length(); 1034 mIntervalPostText.setText(intervalString.substring(postTextStart, 1035 intervalString.length()).trim()); 1036 mIntervalPreText.setText(intervalString.substring(0, markerStart).trim()); 1037 } 1038 } 1039 1040 /** 1041 * Update the "Repeat for N events" end option with the proper string values 1042 * based on the value that has been entered for N. 1043 */ 1044 private void updateEndCountText() { 1045 final String END_COUNT_MARKER = "%d"; 1046 String endString = mResources.getQuantityString(R.plurals.recurrence_end_count, 1047 mModel.endCount); 1048 int markerStart = endString.indexOf(END_COUNT_MARKER); 1049 1050 if (markerStart != -1) { 1051 if (markerStart == 0) { 1052 Log.e(TAG, "No text to put in to recurrence's end spinner."); 1053 } else { 1054 int postTextStart = markerStart + END_COUNT_MARKER.length(); 1055 mPostEndCount.setText(endString.substring(postTextStart, 1056 endString.length()).trim()); 1057 } 1058 } 1059 } 1060 1061 // Implements OnItemSelectedListener interface 1062 // Freq spinner 1063 // End spinner 1064 @Override 1065 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 1066 if (parent == mFreqSpinner) { 1067 mModel.freq = position; 1068 } else if (parent == mEndSpinner) { 1069 switch (position) { 1070 case RecurrenceModel.END_NEVER: 1071 mModel.end = RecurrenceModel.END_NEVER; 1072 break; 1073 case RecurrenceModel.END_BY_DATE: 1074 mModel.end = RecurrenceModel.END_BY_DATE; 1075 break; 1076 case RecurrenceModel.END_BY_COUNT: 1077 mModel.end = RecurrenceModel.END_BY_COUNT; 1078 1079 if (mModel.endCount <= 1) { 1080 mModel.endCount = 1; 1081 } else if (mModel.endCount > COUNT_MAX) { 1082 mModel.endCount = COUNT_MAX; 1083 } 1084 updateEndCountText(); 1085 break; 1086 } 1087 mEndCount.setVisibility(mModel.end == RecurrenceModel.END_BY_COUNT ? View.VISIBLE 1088 : View.GONE); 1089 mEndDateTextView.setVisibility(mModel.end == RecurrenceModel.END_BY_DATE ? View.VISIBLE 1090 : View.GONE); 1091 mPostEndCount.setVisibility( 1092 mModel.end == RecurrenceModel.END_BY_COUNT && !mHidePostEndCount? 1093 View.VISIBLE : View.GONE); 1094 1095 } 1096 updateDialog(); 1097 } 1098 1099 // Implements OnItemSelectedListener interface 1100 @Override 1101 public void onNothingSelected(AdapterView<?> arg0) { 1102 } 1103 1104 @Override 1105 public void onDateSet(DatePickerDialog view, int year, int monthOfYear, int dayOfMonth) { 1106 if (mModel.endDate == null) { 1107 mModel.endDate = new Time(mTime.timezone); 1108 mModel.endDate.hour = mModel.endDate.minute = mModel.endDate.second = 0; 1109 } 1110 mModel.endDate.year = year; 1111 mModel.endDate.month = monthOfYear; 1112 mModel.endDate.monthDay = dayOfMonth; 1113 mModel.endDate.normalize(false); 1114 updateDialog(); 1115 } 1116 1117 // Implements OnCheckedChangeListener interface 1118 // Week repeat by day of week 1119 @Override 1120 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 1121 int itemIdx = -1; 1122 for (int i = 0; i < 7; i++) { 1123 if (itemIdx == -1 && buttonView == mWeekByDayButtons[i]) { 1124 itemIdx = i; 1125 mModel.weeklyByDayOfWeek[i] = isChecked; 1126 } 1127 } 1128 updateDialog(); 1129 } 1130 1131 // Implements android.widget.RadioGroup.OnCheckedChangeListener interface 1132 // Month repeat by radio buttons 1133 @Override 1134 public void onCheckedChanged(RadioGroup group, int checkedId) { 1135 if (checkedId == R.id.repeatMonthlyByNthDayOfMonth) { 1136 mModel.monthlyRepeat = RecurrenceModel.MONTHLY_BY_DATE; 1137 } else if (checkedId == R.id.repeatMonthlyByNthDayOfTheWeek) { 1138 mModel.monthlyRepeat = RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK; 1139 } 1140 updateDialog(); 1141 } 1142 1143 // Implements OnClickListener interface 1144 // EndDate button 1145 // Done button 1146 @Override 1147 public void onClick(View v) { 1148 if (mEndDateTextView == v) { 1149 if (mDatePickerDialog != null) { 1150 mDatePickerDialog.dismiss(); 1151 } 1152 mDatePickerDialog = DatePickerDialog.newInstance(this, mModel.endDate.year, 1153 mModel.endDate.month, mModel.endDate.monthDay); 1154 mDatePickerDialog.setFirstDayOfWeek(Utils.getFirstDayOfWeekAsCalendar(getActivity())); 1155 mDatePickerDialog.setYearRange(Utils.YEAR_MIN, Utils.YEAR_MAX); 1156 mDatePickerDialog.show(getFragmentManager(), FRAG_TAG_DATE_PICKER); 1157 } else if (mDone == v) { 1158 String rrule; 1159 if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) { 1160 rrule = null; 1161 } else { 1162 copyModelToEventRecurrence(mModel, mRecurrence); 1163 rrule = mRecurrence.toString(); 1164 } 1165 mRecurrenceSetListener.onRecurrenceSet(rrule); 1166 dismiss(); 1167 } 1168 } 1169 1170 @Override 1171 public void onActivityCreated(Bundle savedInstanceState) { 1172 super.onActivityCreated(savedInstanceState); 1173 mDatePickerDialog = (DatePickerDialog) getFragmentManager() 1174 .findFragmentByTag(FRAG_TAG_DATE_PICKER); 1175 if (mDatePickerDialog != null) { 1176 mDatePickerDialog.setOnDateSetListener(this); 1177 } 1178 } 1179 1180 public interface OnRecurrenceSetListener { 1181 void onRecurrenceSet(String rrule); 1182 } 1183 1184 public void setOnRecurrenceSetListener(OnRecurrenceSetListener l) { 1185 mRecurrenceSetListener = l; 1186 } 1187 1188 private class EndSpinnerAdapter extends ArrayAdapter<CharSequence> { 1189 final String END_DATE_MARKER = "%s"; 1190 final String END_COUNT_MARKER = "%d"; 1191 1192 private LayoutInflater mInflater; 1193 private int mItemResourceId; 1194 private int mTextResourceId; 1195 private ArrayList<CharSequence> mStrings; 1196 private String mEndDateString; 1197 private boolean mUseFormStrings; 1198 1199 /** 1200 * @param context 1201 * @param textViewResourceId 1202 * @param objects 1203 */ 1204 public EndSpinnerAdapter(Context context, ArrayList<CharSequence> strings, 1205 int itemResourceId, int textResourceId) { 1206 super(context, itemResourceId, strings); 1207 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 1208 mItemResourceId = itemResourceId; 1209 mTextResourceId = textResourceId; 1210 mStrings = strings; 1211 mEndDateString = getResources().getString(R.string.recurrence_end_date); 1212 1213 // If either date or count strings don't translate well, such that we aren't assured 1214 // to have some text available to be placed in the spinner, then we'll have to use 1215 // the more form-like versions of both strings instead. 1216 int markerStart = mEndDateString.indexOf(END_DATE_MARKER); 1217 if (markerStart <= 0) { 1218 // The date string does not have any text before the "%s" so we'll have to use the 1219 // more form-like strings instead. 1220 mUseFormStrings = true; 1221 } else { 1222 String countEndStr = getResources().getQuantityString( 1223 R.plurals.recurrence_end_count, 1); 1224 markerStart = countEndStr.indexOf(END_COUNT_MARKER); 1225 if (markerStart <= 0) { 1226 // The count string does not have any text before the "%d" so we'll have to use 1227 // the more form-like strings instead. 1228 mUseFormStrings = true; 1229 } 1230 } 1231 1232 if (mUseFormStrings) { 1233 // We'll have to set the layout for the spinner to be weight=0 so it doesn't 1234 // take up too much space. 1235 mEndSpinner.setLayoutParams( 1236 new TableLayout.LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)); 1237 } 1238 } 1239 1240 @Override 1241 public View getView(int position, View convertView, ViewGroup parent) { 1242 View v; 1243 // Check if we can recycle the view 1244 if (convertView == null) { 1245 v = mInflater.inflate(mTextResourceId, parent, false); 1246 } else { 1247 v = convertView; 1248 } 1249 1250 TextView item = (TextView) v.findViewById(R.id.spinner_item); 1251 int markerStart; 1252 switch (position) { 1253 case RecurrenceModel.END_NEVER: 1254 item.setText(mStrings.get(RecurrenceModel.END_NEVER)); 1255 break; 1256 case RecurrenceModel.END_BY_DATE: 1257 markerStart = mEndDateString.indexOf(END_DATE_MARKER); 1258 1259 if (markerStart != -1) { 1260 if (mUseFormStrings || markerStart == 0) { 1261 // If we get here, the translation of "Until" doesn't work correctly, 1262 // so we'll just set the whole "Until a date" string. 1263 item.setText(mEndDateLabel); 1264 } else { 1265 item.setText(mEndDateString.substring(0, markerStart).trim()); 1266 } 1267 } 1268 break; 1269 case RecurrenceModel.END_BY_COUNT: 1270 String endString = mResources.getQuantityString(R.plurals.recurrence_end_count, 1271 mModel.endCount); 1272 markerStart = endString.indexOf(END_COUNT_MARKER); 1273 1274 if (markerStart != -1) { 1275 if (mUseFormStrings || markerStart == 0) { 1276 // If we get here, the translation of "For" doesn't work correctly, 1277 // so we'll just set the whole "For a number of events" string. 1278 item.setText(mEndCountLabel); 1279 // Also, we'll hide the " events" that would have been at the end. 1280 mPostEndCount.setVisibility(View.GONE); 1281 // Use this flag so the onItemSelected knows whether to show it later. 1282 mHidePostEndCount = true; 1283 } else { 1284 int postTextStart = markerStart + END_COUNT_MARKER.length(); 1285 mPostEndCount.setText(endString.substring(postTextStart, 1286 endString.length()).trim()); 1287 // In case it's a recycled view that wasn't visible. 1288 if (mModel.end == RecurrenceModel.END_BY_COUNT) { 1289 mPostEndCount.setVisibility(View.VISIBLE); 1290 } 1291 if (endString.charAt(markerStart - 1) == ' ') { 1292 markerStart--; 1293 } 1294 item.setText(endString.substring(0, markerStart).trim()); 1295 } 1296 } 1297 break; 1298 default: 1299 v = null; 1300 break; 1301 } 1302 1303 return v; 1304 } 1305 1306 @Override 1307 public View getDropDownView(int position, View convertView, ViewGroup parent) { 1308 View v; 1309 // Check if we can recycle the view 1310 if (convertView == null) { 1311 v = mInflater.inflate(mItemResourceId, parent, false); 1312 } else { 1313 v = convertView; 1314 } 1315 1316 TextView item = (TextView) v.findViewById(R.id.spinner_item); 1317 item.setText(mStrings.get(position)); 1318 1319 return v; 1320 } 1321 } 1322 } 1323