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