forked from SimplicityApks/ReminderDatePicker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTimeSpinner.java
346 lines (307 loc) · 14.3 KB
/
TimeSpinner.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
package com.simplicityapks.reminderdatepicker.lib;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import com.sleepbot.datetimepicker.time.RadialPickerLayout;
import com.sleepbot.datetimepicker.time.TimePickerDialog;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
/**
* The right PickerSpinner of the Google Keep app, to select a time within one day.
*/
public class TimeSpinner extends PickerSpinner implements AdapterView.OnItemSelectedListener {
/**
* Implement this interface if you want to be notified whenever the selected time changes.
*/
public interface OnTimeSelectedListener {
public void onTimeSelected(int hour, int minute);
}
// These listeners don't have to be implemented, if null just ignore
private OnTimeSelectedListener timeListener = null;
private OnClickListener customTimePicker = null;
// The default time picker dialog to show when the custom one is null:
private TimePickerDialog timePickerDialog;
private FragmentManager fragmentManager;
private boolean showMoreTimeItems = false;
// The time format used to convert Calendars into displayable Strings:
private java.text.DateFormat timeFormat = null;
private int lastSelectedHour = -1;
private int lastSelectedMinute = -1;
/**
* Construct a new TimeSpinner with the given context's theme.
* @param context The Context the view is running in, through which it can access the current theme, resources, etc.
*/
public TimeSpinner(Context context){
this(context, null, 0);
}
/**
* Construct a new TimeSpinner with the given context's theme and the supplied attribute set.
* @param context The Context the view is running in, through which it can access the current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view. May contain a flags attribute.
*/
public TimeSpinner(Context context, AttributeSet attrs){
this(context, attrs, 0);
}
/**
* Construct a new TimeSpinner with the given context's theme, the supplied attribute set, and default style.
* @param context The Context the view is running in, through which it can access the current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view. May contain a flags attribute.
* @param defStyle The default style to apply to this view. If 0, no style will be applied (beyond
* what is included in the theme). This may either be an attribute resource, whose
* value will be retrieved from the current theme, or an explicit style resource.
*/
public TimeSpinner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// check if the parent activity has our timeSelectedListener, automatically enable it:
if(context instanceof OnTimeSelectedListener)
setOnTimeSelectedListener((OnTimeSelectedListener) context);
setOnItemSelectedListener(this);
initTimePickerDialog(context);
// get the FragmentManager:
try{
fragmentManager = ((FragmentActivity) context).getSupportFragmentManager();
} catch (ClassCastException e) {
Log.d(getClass().getSimpleName(), "Can't get fragment manager from context");
}
if(attrs != null) {
// get our flags from xml, if set:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ReminderDatePicker);
int flags = a.getInt(R.styleable.ReminderDatePicker_flags, ReminderDatePicker.MODE_GOOGLE);
setFlags(flags);
a.recycle();
}
}
private void initTimePickerDialog(Context context) {
final Calendar calendar = Calendar.getInstance();
// create the dialog to show later:
timePickerDialog = TimePickerDialog.newInstance(
new TimePickerDialog.OnTimeSetListener() {
@Override
public void onTimeSet(RadialPickerLayout radialPickerLayout, int hour, int minute) {
setSelectedTime(hour, minute);
}
},
calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE),
is24HourFormat(getTimeFormat()), hasVibratePermission(context));
}
private boolean is24HourFormat(java.text.DateFormat timeFormat) {
String pattern;
try {
pattern = ((SimpleDateFormat) timeFormat).toLocalizedPattern();
} catch (ClassCastException e) {
// we cannot get the pattern, use the default setting for out context:
return DateFormat.is24HourFormat(getContext());
}
// if pattern does not contain the 12 hour formats, we return true (regardless of any 'a' (am/pm) modifier)
return !(pattern.contains("h") || pattern.contains("K"));
}
private boolean hasVibratePermission(Context context) {
final String permission = "android.permission.VIBRATE";
final int res = context.checkCallingOrSelfPermission(permission);
return (res == PackageManager.PERMISSION_GRANTED);
}
@Override
public List<TwinTextItem> getSpinnerItems() {
final Resources res = getResources();
ArrayList<TwinTextItem> items = new ArrayList<>(4);
// Morning item:
items.add(new TimeItem(res.getString(R.string.time_morning), formatTime(9, 0), 9, 0));
// Afternoon item:
items.add(new TimeItem(res.getString(R.string.time_afternoon), formatTime(13, 0), 13, 0));
// Evening item:
items.add(new TimeItem(res.getString(R.string.time_evening), formatTime(17, 0), 17, 0));
// Night item:
items.add(new TimeItem(res.getString(R.string.time_night), formatTime(20, 0), 20, 0));
return items;
}
/**
* Gets the currently selected time (that the Spinner is showing)
* @return The selected time as Calendar, or null if there is none.
*/
public Calendar getSelectedTime() {
final Object selectedItem = getSelectedItem();
if(!(selectedItem instanceof TimeItem))
return null;
return ((TimeItem) selectedItem).getTime();
}
/**
* Sets the Spinner's selection as time in hour and minute. If the time was not in the possible
* selections, a temporary item is created and passed to selectTemporary().
* @param hour The hour to be selected.
* @param minute The minute in the hour.
*/
public void setSelectedTime(int hour, int minute) {
final int count = getAdapter().getCount() - 1;
int itemPosition = -1;
for(int i=0; i<count; i++) {
final TimeItem item = ((TimeItem) getAdapter().getItem(i));
if(item.getHour() == hour && item.getMinute() == minute) {
itemPosition = i;
break;
}
}
if(itemPosition >= 0)
setSelection(itemPosition);
else {
// create a temporary TimeItem to select:
selectTemporary(new TimeItem(formatTime(hour, minute), hour, minute));
}
}
private String formatTime(int hour, int minute) {
return getTimeFormat().format(new GregorianCalendar(0,0,0,hour,minute).getTime());
}
/**
* Gets the time format (as java.text.DateFormat) currently used to format Calendar strings.
* Defaults to the short time instance for your locale.
* @return The time format, which will never be null.
*/
public java.text.DateFormat getTimeFormat() {
if(timeFormat == null)
timeFormat = java.text.DateFormat.getTimeInstance(java.text.DateFormat.SHORT);
return timeFormat;
}
/**
* Sets the time format to use for formatting Calendar objects to displayable strings.
* @param timeFormat The new time format (as java.text.DateFormat), or null to use the default format.
*/
public void setTimeFormat(java.text.DateFormat timeFormat) {
this.timeFormat = timeFormat;
// update our pre-built timePickerDialog with the new timeFormat:
initTimePickerDialog(getContext());
// save the flags and selection first:
final PickerSpinnerAdapter adapter = ((PickerSpinnerAdapter)getAdapter());
final boolean moreTimeItems = isShowingMoreTimeItems();
final boolean numbersInView = adapter.isShowingSecondaryTextInView();
final Calendar selection = getSelectedTime();
// we need to restore differently if we have a temporary selection:
final boolean temporarySelected = getSelectedItemPosition() == adapter.getCount();
// to rebuild the spinner items, we need to recreate our adapter:
initAdapter(getContext());
// force restore flags and selection to the new Adapter:
setShowNumbersInView(numbersInView);
this.showMoreTimeItems = false;
if(temporarySelected) {
// for some reason these calls have to be exactly in this order!
setSelectedTime(selection.get(Calendar.HOUR_OF_DAY), selection.get(Calendar.MINUTE));
setShowMoreTimeItems(moreTimeItems);
} else {
// this way it works when a date from the array is selected (like the default)
setShowMoreTimeItems(moreTimeItems);
setSelectedTime(selection.get(Calendar.HOUR_OF_DAY), selection.get(Calendar.MINUTE));
}
}
/**
* Implement this interface if you want to be notified whenever the selected time changes.
*/
public void setOnTimeSelectedListener(OnTimeSelectedListener listener) {
this.timeListener = listener;
}
/**
* Sets a custom listener whose onClick method will be called to create and handle the custom time picker.
* You should call {@link #setSelectedTime} when the custom picker is finished.
* @param launchPicker An {@link android.view.View.OnClickListener} whose onClick method will be
* called to show the custom time picker.
*/
public void setCustomTimePicker(OnClickListener launchPicker) {
this.customTimePicker = launchPicker;
}
/**
* Checks whether the spinner is showing all time items, including noon and late night.
* @return True if FLAG_MORE_TIME has been set or {@link #setShowMoreTimeItems(boolean)} was called, false otherwise.
*/
public boolean isShowingMoreTimeItems() {
return this.showMoreTimeItems;
}
/**
* Toggles showing more time items. If enabled, a noon and a late night time item are shown.
* @param enable True to enable, false to disable more time items.
*/
public void setShowMoreTimeItems(boolean enable) {
if(enable && !showMoreTimeItems) {
// create the noon and late night item:
final Resources res = getResources();
// switch the afternoon item to 2pm:
insertAdapterItem(new TimeItem(res.getString(R.string.time_afternoon_2), formatTime(14, 0), 14, 0), 2);
removeAdapterItemAt(1);
// noon item:
insertAdapterItem(new TimeItem(res.getString(R.string.time_noon), formatTime(12, 0), 12, 0), 1);
// late night item:
addAdapterItem(new TimeItem(res.getString(R.string.time_late_night), formatTime(23, 0), 23, 0));
}
else if(!enable && showMoreTimeItems) {
// switch back the afternoon item:
insertAdapterItem(new TimeItem(getResources().getString(R.string.time_afternoon), formatTime(13, 0), 13, 0), 3);
removeAdapterItemAt(2);
removeAdapterItemAt(1);
removeAdapterItemAt(getLastItemPosition());
}
showMoreTimeItems = enable;
}
/**
* Toggles showing numeric time in the view. Note that time will always be shown in dropdown.
* @param enable True to enable, false to disable numeric mode.
*/
public void setShowNumbersInView(boolean enable) {
PickerSpinnerAdapter adapter = (PickerSpinnerAdapter) getAdapter();
// workaround for now.
if(enable != adapter.isShowingSecondaryTextInView() && getCount() == getSelectedItemPosition())
setSelection(0);
adapter.setShowSecondaryTextInView(enable);
}
/**
* Set the flags to use for this time spinner.
* @param modeOrFlags A mode of ReminderDatePicker.MODE_... or multiple ReminderDatePicker.FLAG_...
* combined with the | operator.
*/
public void setFlags(int modeOrFlags) {
setShowMoreTimeItems((modeOrFlags & ReminderDatePicker.FLAG_MORE_TIME) != 0);
setShowNumbersInView((modeOrFlags & ReminderDatePicker.FLAG_NUMBERS) != 0);
}
@Override
public CharSequence getFooter() {
return getResources().getString(R.string.spinner_time_footer);
}
@Override
public void onFooterClick() {
if (customTimePicker == null) {
timePickerDialog.show(fragmentManager, "TimePickerDialog");
} else {
customTimePicker.onClick(this);
}
}
@Override
protected void restoreTemporarySelection(String codeString) {
selectTemporary(TimeItem.fromString(codeString));
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if(timeListener != null) {
Object selectedObj = getSelectedItem();
if(selectedObj instanceof TimeItem) {
TimeItem selected = (TimeItem) selectedObj;
int hour = selected.getHour();
int minute = selected.getMinute();
if(hour != lastSelectedHour || minute != lastSelectedMinute) {
timeListener.onTimeSelected(hour, minute);
lastSelectedHour = hour;
lastSelectedMinute = minute;
}
}
}
}
// unused
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
}