12 | * Used in Alarm.java as a @TypeConverter
13 | */
14 | public class BooleanArrayConverter {
15 | @TypeConverter
16 | public boolean[] fromString(String value) {
17 | boolean[] arr = new boolean[7];
18 | if (value.equals("")) {
19 | return arr;
20 | }
21 |
22 | List
10 | * Used in Alarm.java as a @TypeConverter
11 | */
12 | public class LatLngConverter {
13 | @TypeConverter
14 | public LatLng fromString(String value) {
15 | String[] splitValue = value.split(":");
16 | return new LatLng(
17 | Double.parseDouble(splitValue[0]),
18 | Double.parseDouble(splitValue[1])
19 | );
20 | }
21 |
22 | @TypeConverter
23 | public String latLngToString(LatLng latLng) {
24 | return latLng.latitude + ":" + latLng.longitude;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/manveerbasra/ontime/timehandlers/TimeShiftHandler.java:
--------------------------------------------------------------------------------
1 | package com.manveerbasra.ontime.timehandlers;
2 |
3 | import android.util.Log;
4 |
5 | import com.google.android.gms.maps.model.LatLng;
6 |
7 | /**
8 | * Class to get and handle time shift required for user to be alerted OnTime
9 | */
10 | public class TimeShiftHandler {
11 |
12 | private final String TAG = "TimeShiftHandler";
13 |
14 | private TrafficTimeHandler mTrafficTimeHandler;
15 | private WeatherTimeHandler mWeatherTimeHandler;
16 |
17 | public TimeShiftHandler(String mapsApiKey, String weatherApiKey) {
18 | mTrafficTimeHandler = new TrafficTimeHandler(mapsApiKey);
19 | mWeatherTimeHandler = new WeatherTimeHandler(weatherApiKey);
20 | }
21 |
22 | /**
23 | * Get the alarm's time shift derived from current traffic and weather conditions
24 | *
25 | * @param start starting LatLng point
26 | * @param end destination LatLng point
27 | * @param departureTimeInSecs time in seconds since epoch of expected departure time
28 | * @param transMode String of transportation mode
29 | * @return long alarm time shift in milliseconds
30 | */
31 | public long getTimeShiftInMillis(LatLng start, LatLng end, int departureTimeInSecs, String transMode) {
32 | long trafficShift = mTrafficTimeHandler.getTimeShiftInMillis(start, end, departureTimeInSecs, transMode);
33 | long weatherShift = mWeatherTimeHandler.getTimeShiftInMillis(start, end);
34 |
35 | long totalShift = (long) (trafficShift + weatherShift);
36 |
37 | Log.i(TAG, "Total Time shift: " + totalShift +
38 | " = (" + trafficShift + " + " + weatherShift + ")");
39 | return totalShift;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/manveerbasra/ontime/timehandlers/TrafficTimeHandler.java:
--------------------------------------------------------------------------------
1 | package com.manveerbasra.ontime.timehandlers;
2 |
3 | import android.os.AsyncTask;
4 | import android.util.Log;
5 |
6 | import com.google.android.gms.maps.model.LatLng;
7 | import com.manveerbasra.ontime.util.JSONParser;
8 |
9 | import org.json.JSONException;
10 | import org.json.JSONObject;
11 |
12 | import java.io.BufferedReader;
13 | import java.io.IOException;
14 | import java.io.InputStream;
15 | import java.io.InputStreamReader;
16 | import java.net.HttpURLConnection;
17 | import java.net.URL;
18 | import java.util.HashMap;
19 | import java.util.concurrent.ExecutionException;
20 | import java.util.concurrent.TimeUnit;
21 |
22 | /**
23 | * Class to get and handle time shift for alarm's origin -> destination commute
24 | */
25 | public class TrafficTimeHandler {
26 |
27 | private final String TAG = "TrafficTimeHandler";
28 |
29 | // keys for HashMap of parsed JSON data
30 | public static final String DURATION = "duration";
31 | public static final String DURATION_TRAFFIC = "duration_in_traffic";
32 |
33 | private String mApiKey;
34 |
35 | TrafficTimeHandler(String apiKey) {
36 | this.mApiKey = apiKey;
37 | }
38 |
39 | /**
40 | * Get the difference between the usual and today's commute duration
41 | *
42 | * @param start starting LatLng point
43 | * @param end destination LatLng point
44 | * @param departureTimeInSecs time in seconds since epoch of expected departure time
45 | * @param transMode String of transportation mode
46 | * @return long time difference between usual and today's commute in milliseconds
47 | */
48 | long getTimeShiftInMillis(LatLng start, LatLng end, int departureTimeInSecs, String transMode) {
49 |
50 | String jsonData;
51 | HashMap
25 | * Implemented in AddAlarmActivity
26 | */
27 | public interface OnDialogCompleteListener {
28 | void onDialogComplete(boolean[] selectedDays);
29 | }
30 |
31 | private OnDialogCompleteListener completeListener;
32 |
33 | @Override
34 | public void onAttach(Activity activity) {
35 | super.onAttach(activity);
36 | try {
37 | this.completeListener = (OnDialogCompleteListener) activity;
38 | } catch (final ClassCastException e) {
39 | throw new ClassCastException(activity.toString() + " must implement OnCompleteListener");
40 | }
41 | }
42 |
43 | // End of DialogCompleteListener methods
44 |
45 | private boolean[] activeDays;
46 |
47 | @Override
48 | public void onCreate(Bundle savedInstanceState) {
49 | super.onCreate(savedInstanceState);
50 | activeDays = getArguments().getBooleanArray("activeDays");
51 | }
52 |
53 | @NonNull
54 | @Override
55 | public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
56 | AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
57 | // Set the dialog title
58 | builder.setTitle(R.string.set_repeat);
59 | // Specify the list array, the items to be selected by default (null for none),
60 | // and the listener through which to receive callbacks when items are selected
61 | builder.setMultiChoiceItems(R.array.days_of_week, activeDays,
62 | new DialogInterface.OnMultiChoiceClickListener() {
63 | @Override
64 | public void onClick(DialogInterface dialog, int selectedDay,
65 | boolean isChecked) {
66 | activeDays[selectedDay] = isChecked;
67 | }
68 | });
69 | // Set the action buttons
70 | builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
71 | @Override
72 | public void onClick(DialogInterface dialog, int id) {
73 | // Return selectedDays to activity
74 | saveSelectedDays(activeDays);
75 | }
76 | });
77 | builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
78 | @Override
79 | public void onClick(DialogInterface dialog, int id) {
80 | }
81 | });
82 |
83 | return builder.create();
84 | }
85 |
86 | /**
87 | * Saved selectedDays by calling completeListener's complete function
88 | *
89 | * @param selectedDays ArrayList of selected days ints
90 | */
91 | private void saveSelectedDays(boolean[] selectedDays) {
92 | this.completeListener.onDialogComplete(selectedDays);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/java/com/manveerbasra/ontime/ui/activity/AddAlarmActivity.java:
--------------------------------------------------------------------------------
1 | package com.manveerbasra.ontime.ui.activity;
2 |
3 | import android.app.TimePickerDialog;
4 | import android.arch.lifecycle.ViewModelProviders;
5 | import android.content.Intent;
6 | import android.support.annotation.NonNull;
7 | import android.support.annotation.Nullable;
8 | import android.support.design.widget.FloatingActionButton;
9 | import android.support.design.widget.Snackbar;
10 | import android.support.v7.app.AppCompatActivity;
11 | import android.os.Bundle;
12 | import android.view.Menu;
13 | import android.view.MenuItem;
14 | import android.view.View;
15 | import android.widget.ImageButton;
16 | import android.widget.RelativeLayout;
17 | import android.widget.TextView;
18 | import android.widget.TimePicker;
19 |
20 | import com.manveerbasra.ontime.R;
21 | import com.manveerbasra.ontime.db.Alarm;
22 | import com.manveerbasra.ontime.ui.SetRepeatDaysDialogFragment;
23 | import com.manveerbasra.ontime.viewmodel.AlarmViewModel;
24 |
25 | import java.util.Calendar;
26 | import java.util.GregorianCalendar;
27 |
28 | /**
29 | * Used to create and edit alarms, depending on REQUEST_CODE
30 | */
31 | public class AddAlarmActivity extends AppCompatActivity implements SetRepeatDaysDialogFragment.OnDialogCompleteListener {
32 |
33 | private final String TAG = "AddAlarmActivity";
34 |
35 | private static final int SET_START_LOCATION_ACTIVITY_REQUEST_CODE = 1;
36 | private static final int SET_END_LOCATION_ACTIVITY_REQUEST_CODE = 2;
37 |
38 | // Key values for returning intent.
39 | public static final String EXTRA_BUNDLE = "com.manveerbasra.ontime.AddAlarmActivity.BUNDLE";
40 | public static final String EXTRA_ALARM = "com.manveerbasra.ontime.AddAlarmActivity.ALARM";
41 | public static final String EXTRA_DELETE = "com.manveerbasra.ontime.AddAlarmActivity.DELETE";
42 |
43 | private AlarmViewModel alarmViewModel;
44 | private int mCurrRequestCode; // current request code - static values in MainActivity
45 |
46 | private Alarm mAlarm;
47 | // Data objects
48 | private Calendar calendar;
49 | // View objects
50 | private TextView mTimeTextView;
51 | private TextView mRepeatTextView;
52 | private TextView mStartLocTextView;
53 | private TextView mEndLocTextView;
54 | private FloatingActionButton mDeleteButton;
55 |
56 |
57 | @Override
58 | protected void onCreate(Bundle savedInstanceState) {
59 | super.onCreate(savedInstanceState);
60 | setContentView(R.layout.activity_add_alarm);
61 |
62 | calendar = Calendar.getInstance();
63 | mDeleteButton = findViewById(R.id.fab_add_alarm_delete);
64 |
65 | mTimeTextView = findViewById(R.id.add_alarm_time_text);
66 | mRepeatTextView = findViewById(R.id.add_alarm_repeat_text);
67 | mStartLocTextView = findViewById(R.id.add_alarm_start_loc_text);
68 | mEndLocTextView = findViewById(R.id.add_alarm_end_loc_text);
69 |
70 | // Get a new or existing ViewModel from the ViewModelProvider.
71 | alarmViewModel = ViewModelProviders.of(this).get(AlarmViewModel.class);
72 |
73 | Intent intent = getIntent();
74 | if (intent.hasExtra(EXTRA_BUNDLE)) { // Activity called to edit an alarm.
75 | Bundle args = intent.getBundleExtra(EXTRA_BUNDLE);
76 | mAlarm = args.getParcelable(EXTRA_ALARM);
77 | mCurrRequestCode = MainActivity.EDIT_ALARM_ACTIVITY_REQUEST_CODE;
78 | } else {
79 | mCurrRequestCode = MainActivity.NEW_ALARM_ACTIVITY_REQUEST_CODE;
80 | }
81 |
82 | if (mAlarm != null) {
83 | mStartLocTextView.setText(mAlarm.startPlace);
84 | mEndLocTextView.setText(mAlarm.endPlace);
85 | mTimeTextView.setText(mAlarm.getStringTime());
86 | mRepeatTextView.setText(mAlarm.getStringOfActiveDays());
87 | setTitle(R.string.edit_alarm);
88 | addModeButtonListeners(mAlarm.transMode);
89 |
90 | addDeleteButtonListener();
91 | } else {
92 | mAlarm = new Alarm();
93 | mAlarm.activeDays = new boolean[7];
94 | mAlarm.transMode = "driving";
95 | mDeleteButton.hide();
96 | setInitialAlarmTime();
97 | addModeButtonListeners("driving");
98 | mRepeatTextView.setText(R.string.never);
99 | }
100 |
101 | addSetTimeLayoutListener();
102 | addSetRepeatLayoutListener();
103 | addSetStartLocationListener();
104 | addEndStartLocationListener();
105 | }
106 |
107 |
108 | /**
109 | * Initialize TimeTextView and Alarm's time with current time
110 | */
111 | private void setInitialAlarmTime() {
112 | // Get time and set it to alarm time TextView
113 | int hour = calendar.get(Calendar.HOUR_OF_DAY);
114 | int minute = calendar.get(Calendar.MINUTE);
115 |
116 | String currentTime = getFormattedTime(hour, minute);
117 | mTimeTextView.setText(currentTime);
118 | mAlarm.setTime(currentTime);
119 | }
120 |
121 | private void addDeleteButtonListener() {
122 | mDeleteButton.setOnClickListener(new View.OnClickListener() {
123 | @Override
124 | public void onClick(View view) {
125 | Intent replyIntent = new Intent();
126 |
127 | alarmViewModel.delete(mAlarm);
128 | replyIntent.putExtra(EXTRA_DELETE, true);
129 |
130 | setResult(RESULT_OK, replyIntent);
131 | finish();
132 | }
133 | });
134 | }
135 |
136 | /**
137 | * When timeChangeLayout is selected, open TimePickerDialog
138 | */
139 | private void addSetTimeLayoutListener() {
140 | // Get layout view
141 | RelativeLayout setTimeButton = findViewById(R.id.add_alarm_time_layout);
142 |
143 | setTimeButton.setOnClickListener(new View.OnClickListener() {
144 | @Override
145 | public void onClick(View view) {
146 |
147 | // Get initial hour and minute values to display in dialog;
148 | int hour, minute;
149 | if (mAlarm.time == null) {
150 | hour = calendar.get(Calendar.HOUR_OF_DAY);
151 | minute = calendar.get(Calendar.MINUTE);
152 | } else {
153 | Calendar calendar = GregorianCalendar.getInstance();
154 | calendar.setTime(mAlarm.time);
155 | hour = calendar.get(Calendar.HOUR_OF_DAY);
156 | minute = calendar.get(Calendar.MINUTE);
157 | }
158 |
159 | TimePickerDialog timePicker;
160 | timePicker = new TimePickerDialog(AddAlarmActivity.this, new TimePickerDialog.OnTimeSetListener() {
161 | @Override
162 | public void onTimeSet(TimePicker timePicker, int selectedHour, int selectedMinute) {
163 | String formattedTime = getFormattedTime(selectedHour, selectedMinute);
164 | mAlarm.setTime(formattedTime);
165 | mTimeTextView.setText(formattedTime);
166 | }
167 | }, hour, minute, false);
168 | timePicker.setTitle("Select Time");
169 | timePicker.show();
170 | }
171 | });
172 | }
173 |
174 | /**
175 | * When repeatChangeLayout is selected, open SetRepeatDaysDialogFragment
176 | */
177 | private void addSetRepeatLayoutListener() {
178 | // Get layout view
179 | RelativeLayout setRepeatButton = findViewById(R.id.add_alarm_repeat_layout);
180 |
181 | setRepeatButton.setOnClickListener(new View.OnClickListener() {
182 | @Override
183 | public void onClick(View view) {
184 | SetRepeatDaysDialogFragment setRepeatDaysDialogFragment = new SetRepeatDaysDialogFragment();
185 |
186 | Bundle args = getBundle();
187 | setRepeatDaysDialogFragment.setArguments(args);
188 | // Display dialog
189 | setRepeatDaysDialogFragment.show(getSupportFragmentManager(), "A");
190 | }
191 | });
192 | }
193 |
194 | /**
195 | * When startLocation Layout is selected, open maps activity
196 | */
197 | private void addSetStartLocationListener() {
198 | RelativeLayout setStartLocButton = findViewById(R.id.add_alarm_start_loc_layout);
199 |
200 | setStartLocButton.setOnClickListener(new View.OnClickListener() {
201 | @Override
202 | public void onClick(View view) {
203 | Intent intent = new Intent(AddAlarmActivity.this, MapsActivity.class);
204 |
205 | Bundle args = new Bundle();
206 | args.putParcelable(MapsActivity.EXTRA_LATLNG, mAlarm.startPoint);
207 | intent.putExtra(MapsActivity.BUNDLE_POINT, args);
208 | intent.putExtra(MapsActivity.EXTRA_PLACE, mAlarm.startPlace);
209 |
210 | startActivityForResult(intent, SET_START_LOCATION_ACTIVITY_REQUEST_CODE);
211 | }
212 | });
213 | }
214 |
215 | /**
216 | * When endLocation Layout is selected, open maps activity
217 | */
218 | private void addEndStartLocationListener() {
219 | RelativeLayout setEndLocButton = findViewById(R.id.add_alarm_end_loc_layout);
220 |
221 | setEndLocButton.setOnClickListener(new View.OnClickListener() {
222 | @Override
223 | public void onClick(View view) {
224 | Intent intent = new Intent(AddAlarmActivity.this, MapsActivity.class);
225 |
226 | Bundle args = new Bundle();
227 | args.putParcelable(MapsActivity.EXTRA_LATLNG, mAlarm.endPoint);
228 | intent.putExtra(MapsActivity.BUNDLE_POINT, args);
229 | intent.putExtra(MapsActivity.EXTRA_PLACE, mAlarm.endPlace);
230 |
231 | startActivityForResult(intent, SET_END_LOCATION_ACTIVITY_REQUEST_CODE);
232 | }
233 | });
234 | }
235 |
236 | private void addModeButtonListeners(String currMode) {
237 | final ImageButton walkButton = findViewById(R.id.mode_walk_button);
238 | final ImageButton bikeButton = findViewById(R.id.mode_bike_button);
239 | final ImageButton transitButton = findViewById(R.id.mode_transit_button);
240 | final ImageButton driveButton = findViewById(R.id.mode_drive_button);
241 |
242 | if (currMode == null) {
243 | currMode = "driving";
244 | }
245 |
246 | switch (currMode) { // Set initial backgrounds based on currMode parameter
247 | case "driving":
248 | updateBackgrounds(driveButton, walkButton, bikeButton, transitButton);
249 | break;
250 | case "transit":
251 | updateBackgrounds(transitButton, walkButton, bikeButton, driveButton);
252 | break;
253 | case "bicycling":
254 | updateBackgrounds(bikeButton, walkButton, transitButton, driveButton);
255 | break;
256 | case "walking":
257 | updateBackgrounds(walkButton, bikeButton, transitButton, driveButton);
258 | break;
259 | }
260 |
261 | walkButton.setOnClickListener(new View.OnClickListener() {
262 | @Override
263 | public void onClick(View view) {
264 | updateBackgrounds(walkButton, bikeButton, transitButton, driveButton);
265 | mAlarm.transMode = "walking";
266 | }
267 | });
268 |
269 | bikeButton.setOnClickListener(new View.OnClickListener() {
270 | @Override
271 | public void onClick(View view) {
272 | updateBackgrounds(bikeButton, walkButton, transitButton, driveButton);
273 | mAlarm.transMode = "bicycling";
274 | }
275 | });
276 |
277 | transitButton.setOnClickListener(new View.OnClickListener() {
278 | @Override
279 | public void onClick(View view) {
280 | updateBackgrounds(transitButton, walkButton, bikeButton, driveButton);
281 | mAlarm.transMode = "transit";
282 | }
283 | });
284 |
285 | driveButton.setOnClickListener(new View.OnClickListener() {
286 | @Override
287 | public void onClick(View view) {
288 | updateBackgrounds(driveButton, walkButton, bikeButton, transitButton);
289 | mAlarm.transMode = "driving";
290 | }
291 | });
292 | }
293 |
294 | /**
295 | * Update backgrounds of different ImageButtons based on whether they should be active or not.
296 | * First parameter button is set to active
297 | */
298 | private void updateBackgrounds(ImageButton active, ImageButton inactive1, ImageButton inactive2, ImageButton inactive3) {
299 | active.setBackground(getDrawable(R.drawable.solid_circle_blue));
300 | inactive1.setBackground(getDrawable(R.drawable.solid_circle_grey));
301 | inactive2.setBackground(getDrawable(R.drawable.solid_circle_grey));
302 | inactive3.setBackground(getDrawable(R.drawable.solid_circle_grey));
303 | }
304 |
305 | /**
306 | * Get Bundle of arguments for SetRepeatDaysDialogFragment, arguments include alarm's active days.
307 | */
308 | @NonNull
309 | private Bundle getBundle() {
310 | Bundle args = new Bundle();
311 |
312 | args.putBooleanArray("activeDays", mAlarm.activeDays);
313 | return args;
314 | }
315 |
316 | /**
317 | * This method is called when SetRepeatDaysDialogFragment completes, we get the selectedDays
318 | * and apply that to alarm
319 | *
320 | * @param selectedDaysBools boolean array of selected days of the week
321 | */
322 | public void onDialogComplete(boolean[] selectedDaysBools) {
323 | mAlarm.activeDays = selectedDaysBools;
324 | String formattedActiveDays = Alarm.getStringOfActiveDays(mAlarm.activeDays);
325 | mRepeatTextView.setText(formattedActiveDays);
326 | }
327 |
328 | @Override
329 | public boolean onCreateOptionsMenu(Menu menu) {
330 | // Inflate the menu; this adds items to the action bar if it is present.
331 | getMenuInflater().inflate(R.menu.menu_add_alarm, menu);
332 | return true;
333 | }
334 |
335 | @Override
336 | public boolean onOptionsItemSelected(MenuItem item) {
337 | int id = item.getItemId();
338 |
339 | //noinspection SimplifiableIfStatement
340 | if (id == R.id.action_alarm_save) {
341 | if (mStartLocTextView.getText().equals(getString(R.string.set_start_location))
342 | || mEndLocTextView.getText().equals(getString(R.string.set_end_location))) {
343 | Snackbar.make(findViewById(R.id.fab_add_alarm_delete), getString(R.string.locs_not_selected), Snackbar.LENGTH_SHORT).show();
344 | } else {
345 | Intent replyIntent = new Intent();
346 |
347 | if (mCurrRequestCode == MainActivity.EDIT_ALARM_ACTIVITY_REQUEST_CODE) {
348 | alarmViewModel.update(mAlarm);
349 | } else {
350 | alarmViewModel.insert(mAlarm);
351 | }
352 |
353 | setResult(RESULT_OK, replyIntent);
354 | finish();
355 | }
356 | }
357 |
358 | return super.onOptionsItemSelected(item);
359 | }
360 |
361 | /**
362 | * Updates UI with data received from MapActivity
363 | *
364 | * @param requestCode request code varies on whether start or end location set
365 | * @param resultCode whether activity successfully completed
366 | * @param data reply Intent, contains extras that vary based on request code
367 | */
368 | @Override
369 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
370 | super.onActivityResult(requestCode, resultCode, data);
371 |
372 | if (data == null) {
373 | return;
374 | }
375 |
376 | if (requestCode == SET_START_LOCATION_ACTIVITY_REQUEST_CODE && resultCode == RESULT_OK) {
377 | // Get extras
378 | mAlarm.startPlace = data.getStringExtra(MapsActivity.EXTRA_PLACE);
379 | Bundle args = data.getBundleExtra(MapsActivity.BUNDLE_POINT);
380 | mAlarm.startPoint = args.getParcelable(MapsActivity.EXTRA_LATLNG);
381 |
382 | // Set place to start location textView
383 | mStartLocTextView.setText(mAlarm.startPlace);
384 |
385 | } else if (requestCode == SET_END_LOCATION_ACTIVITY_REQUEST_CODE && resultCode == RESULT_OK) {
386 | // Get extra
387 | mAlarm.endPlace = data.getStringExtra(MapsActivity.EXTRA_PLACE);
388 | Bundle args = data.getBundleExtra(MapsActivity.BUNDLE_POINT);
389 | mAlarm.endPoint = args.getParcelable(MapsActivity.EXTRA_LATLNG);
390 | // Set place to start location textView
391 | mEndLocTextView.setText(mAlarm.endPlace);
392 | }
393 | }
394 |
395 | /**
396 | * Return a string of the form hh:mm aa from given hour and minute
397 | *
398 | * @param hour integer of hour in 24h format
399 | * @param minute integer of minute
400 | * @return a String of formatted time
401 | */
402 | private String getFormattedTime(int hour, int minute) {
403 | String meridian = "AM";
404 | if (hour >= 12) {
405 | meridian = "PM";
406 | }
407 |
408 | if (hour > 12) {
409 | hour -= 12;
410 | } else if (hour == 0) {
411 | hour = 12;
412 | }
413 |
414 | String formattedTime;
415 | if (minute < 10) {
416 | formattedTime = hour + ":0" + minute + " " + meridian;
417 | } else {
418 | formattedTime = hour + ":" + minute + " " + meridian;
419 | }
420 |
421 | return formattedTime;
422 | }
423 | }
424 |
--------------------------------------------------------------------------------
/app/src/main/java/com/manveerbasra/ontime/ui/activity/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.manveerbasra.ontime.ui.activity;
2 |
3 | import android.arch.lifecycle.Observer;
4 | import android.arch.lifecycle.ViewModelProviders;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.support.annotation.Nullable;
8 | import android.support.design.widget.FloatingActionButton;
9 | import android.support.design.widget.Snackbar;
10 | import android.support.v7.app.AppCompatActivity;
11 | import android.support.v7.widget.LinearLayoutManager;
12 | import android.support.v7.widget.RecyclerView;
13 | import android.support.v7.widget.Toolbar;
14 | import android.view.View;
15 | import android.view.Menu;
16 | import android.view.MenuItem;
17 |
18 | import com.google.android.gms.maps.model.LatLng;
19 | import com.manveerbasra.ontime.R;
20 | import com.manveerbasra.ontime.db.Alarm;
21 | import com.manveerbasra.ontime.ui.AlarmListAdapter;
22 | import com.manveerbasra.ontime.viewmodel.AlarmViewModel;
23 |
24 | import java.text.DateFormat;
25 | import java.text.SimpleDateFormat;
26 | import java.util.Date;
27 | import java.util.List;
28 |
29 | public class MainActivity extends AppCompatActivity {
30 |
31 | private final String TAG = "MainActivity";
32 |
33 | public static final int NEW_ALARM_ACTIVITY_REQUEST_CODE = 1;
34 | public static final int EDIT_ALARM_ACTIVITY_REQUEST_CODE = 2;
35 |
36 | /**
37 | * Used to access AlarmDatabase
38 | */
39 | private AlarmViewModel alarmViewModel;
40 | private View snackbarAnchor;
41 |
42 | @Override
43 | protected void onCreate(Bundle savedInstanceState) {
44 | super.onCreate(savedInstanceState);
45 | setContentView(R.layout.activity_main);
46 | Toolbar toolbar = findViewById(R.id.toolbar_main);
47 | setSupportActionBar(toolbar);
48 | snackbarAnchor = findViewById(R.id.fab);
49 |
50 | // Setup adapter to display alarms.
51 | RecyclerView recyclerView = findViewById(R.id.alarm_list);
52 | final AlarmListAdapter adapter = new AlarmListAdapter(this);
53 | recyclerView.setAdapter(adapter);
54 | recyclerView.setLayoutManager(new LinearLayoutManager(this));
55 |
56 | // Get a new or existing ViewModel from the ViewModelProvider.
57 | alarmViewModel = ViewModelProviders.of(this).get(AlarmViewModel.class);
58 |
59 | // Add an observer on the LiveData returned by getAllAlarms.
60 | alarmViewModel.getAllAlarms().observe(this, new Observer
88 | * Handles both new Alarms and edited Alarms.
89 | *
90 | * @param requestCode request code varies on whether alarm is added or edited
91 | * @param resultCode whether activity successfully completed
92 | * @param data reply Intent, contains extras
93 | */
94 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
95 | super.onActivityResult(requestCode, resultCode, data);
96 |
97 | if (resultCode == RESULT_OK) {
98 | if (data.hasExtra(AddAlarmActivity.EXTRA_DELETE)) {
99 | Snackbar.make(snackbarAnchor, R.string.alarm_deleted, Snackbar.LENGTH_SHORT).show();
100 | } else {
101 | Snackbar.make(snackbarAnchor, R.string.alarm_saved, Snackbar.LENGTH_SHORT).show();
102 | }
103 |
104 | } else {
105 | Snackbar.make(snackbarAnchor, R.string.alarm_not_saved, Snackbar.LENGTH_SHORT).show();
106 | }
107 | }
108 |
109 | @Override
110 | public boolean onCreateOptionsMenu(Menu menu) {
111 | // Inflate the menu; this adds items to the action bar if it is present.
112 | getMenuInflater().inflate(R.menu.menu_main, menu);
113 | return true;
114 | }
115 |
116 | @Override
117 | public boolean onOptionsItemSelected(MenuItem item) {
118 | int id = item.getItemId();
119 |
120 | //noinspection SimplifiableIfStatement
121 | if (id == R.id.action_settings) {
122 | Intent intent = new Intent(MainActivity.this, SettingsActivity.class);
123 | startActivity(intent);
124 | return true;
125 | }
126 |
127 | return super.onOptionsItemSelected(item);
128 | }
129 |
130 |
131 | }
132 |
--------------------------------------------------------------------------------
/app/src/main/java/com/manveerbasra/ontime/ui/activity/MapsActivity.java:
--------------------------------------------------------------------------------
1 | package com.manveerbasra.ontime.ui.activity;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.design.widget.FloatingActionButton;
6 | import android.support.design.widget.Snackbar;
7 | import android.support.v4.app.FragmentActivity;
8 | import android.util.Log;
9 | import android.view.View;
10 |
11 | import com.google.android.gms.common.api.Status;
12 | import com.google.android.gms.location.places.Place;
13 | import com.google.android.gms.location.places.ui.PlaceAutocompleteFragment;
14 | import com.google.android.gms.location.places.ui.PlaceSelectionListener;
15 | import com.google.android.gms.maps.CameraUpdateFactory;
16 | import com.google.android.gms.maps.GoogleMap;
17 | import com.google.android.gms.maps.OnMapReadyCallback;
18 | import com.google.android.gms.maps.SupportMapFragment;
19 | import com.google.android.gms.maps.model.CameraPosition;
20 | import com.google.android.gms.maps.model.LatLng;
21 | import com.google.android.gms.maps.model.Marker;
22 | import com.google.android.gms.maps.model.MarkerOptions;
23 | import com.manveerbasra.ontime.R;
24 |
25 | /**
26 | * Activity to allow user to choose a location with map aid
27 | */
28 | public class MapsActivity extends FragmentActivity implements OnMapReadyCallback {
29 |
30 | private final String TAG = "MapsActivity";
31 |
32 | public static final String EXTRA_PLACE = "com.manveerbasra.ontime.MapsActivity.PLACE";
33 | public static final String BUNDLE_POINT = "com.manveerbasra.ontime.MapsActivity.BUNDLE.POINT";
34 | public static final String EXTRA_LATLNG = "com.manveerbasra.ontime.MapsActivity.LATLNG";
35 |
36 | private GoogleMap mMap;
37 | private Marker mMarker;
38 |
39 | private LatLng mPoint;
40 | private String mPlace;
41 |
42 | @Override
43 | protected void onCreate(Bundle savedInstanceState) {
44 | super.onCreate(savedInstanceState);
45 | setContentView(R.layout.activity_maps);
46 |
47 | Intent intent = getIntent();
48 | Bundle args = intent.getBundleExtra(BUNDLE_POINT);
49 | mPoint = args.getParcelable(EXTRA_LATLNG);
50 | mPlace = intent.getStringExtra(EXTRA_PLACE);
51 |
52 | setPlaceSearchBarListener();
53 | setFABClickListener();
54 |
55 | // Obtain the SupportMapFragment and get notified when the map is ready to be used.
56 | SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
57 | .findFragmentById(R.id.map);
58 | if (mapFragment != null) mapFragment.getMapAsync(this);
59 | }
60 |
61 | /**
62 | * Setup onPlaceSelected Listener for the Place Search Bar
63 | */
64 | private void setPlaceSearchBarListener() {
65 | PlaceAutocompleteFragment placeAutoComplete = (PlaceAutocompleteFragment) getFragmentManager().findFragmentById(R.id.place_autocomplete);
66 | placeAutoComplete.setOnPlaceSelectedListener(new PlaceSelectionListener() {
67 | @Override
68 | public void onPlaceSelected(Place place) {
69 | if (mMarker != null) mMarker.remove(); // remove old marker
70 |
71 | // Add marker in selected place
72 | LatLng latLng = place.getLatLng();
73 | mMarker = mMap.addMarker(new MarkerOptions().position(latLng).title(place.getName().toString()));
74 |
75 | // Animate camera's movement to selected place
76 | CameraPosition cameraPosition = new CameraPosition.Builder()
77 | .target(latLng) // Sets the center of the map to place's coordinates
78 | .zoom(14) // Sets the zoom
79 | .build(); // Creates a CameraPosition from the builder
80 | mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
81 |
82 | Log.d(TAG, "Place selected: " + place.getName());
83 | }
84 |
85 | @Override
86 | public void onError(Status status) {
87 | Log.d(TAG, "An error occurred: " + status);
88 | }
89 | });
90 | }
91 |
92 | /**
93 | * Setup onClick Listener for FAB save button
94 | */
95 | public void setFABClickListener() {
96 | FloatingActionButton fab = findViewById(R.id.fab_loc_save);
97 | fab.setOnClickListener(new View.OnClickListener() {
98 | @Override
99 | public void onClick(View view) {
100 | if (mMarker != null) {
101 | Intent replyIntent = new Intent();
102 |
103 | Bundle args = new Bundle();
104 | args.putParcelable(EXTRA_LATLNG, mMarker.getPosition());
105 |
106 | // Add extras
107 | replyIntent.putExtra(BUNDLE_POINT, args);
108 | replyIntent.putExtra(EXTRA_PLACE, mMarker.getTitle());
109 | setResult(RESULT_OK, replyIntent);
110 | finish();
111 | } else {
112 | Snackbar.make(findViewById(R.id.fab_loc_save), getString(R.string.loc_not_selected), Snackbar.LENGTH_SHORT).show();
113 | }
114 | }
115 | });
116 | }
117 |
118 |
119 | /**
120 | * Manipulates the map once available.
121 | * This callback is triggered when the map is ready to be used.
122 | * If Google Play services is not installed on the device, the user will be prompted to install
123 | * it inside the SupportMapFragment. This method will only be triggered once the user has
124 | * installed Google Play services and returned to the app.
125 | */
126 | @Override
127 | public void onMapReady(GoogleMap googleMap) {
128 | mMap = googleMap;
129 | if (mPoint != null) { // add starting marker and move camera
130 | mMarker = mMap.addMarker(new MarkerOptions().position(mPoint).title(mPlace));
131 | mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mPoint, 14));
132 | }
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/app/src/main/java/com/manveerbasra/ontime/ui/activity/SettingsActivity.java:
--------------------------------------------------------------------------------
1 | package com.manveerbasra.ontime.ui.activity;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.res.Configuration;
7 | import android.media.Ringtone;
8 | import android.media.RingtoneManager;
9 | import android.net.Uri;
10 | import android.os.Build;
11 | import android.os.Bundle;
12 | import android.preference.ListPreference;
13 | import android.preference.Preference;
14 | import android.preference.PreferenceActivity;
15 | import android.support.v7.app.ActionBar;
16 | import android.preference.PreferenceFragment;
17 | import android.preference.PreferenceManager;
18 | import android.preference.RingtonePreference;
19 | import android.text.TextUtils;
20 | import android.view.MenuItem;
21 | import android.support.v4.app.NavUtils;
22 |
23 | import com.manveerbasra.ontime.R;
24 | import com.manveerbasra.ontime.ui.AppCompatPreferenceActivity;
25 |
26 | import java.util.List;
27 |
28 | /**
29 | * A {@link PreferenceActivity} that presents a set of application settings. On
30 | * handset devices, settings are presented as a single list. On tablets,
31 | * settings are split by category, with category headers shown to the left of
32 | * the list of settings.
33 | *
34 | * See
35 | * Android Design: Settings for design guidelines and the Settings
37 | * API Guide for more information on developing a Settings UI.
38 | */
39 | public class SettingsActivity extends AppCompatPreferenceActivity {
40 |
41 | /**
42 | * A preference value change listener that updates the preference's summary
43 | * to reflect its new value.
44 | */
45 | private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
46 | @Override
47 | public boolean onPreferenceChange(Preference preference, Object value) {
48 | String stringValue = value.toString();
49 |
50 | if (preference instanceof ListPreference) {
51 | // For list preferences, look up the correct display value in
52 | // the preference's 'entries' list.
53 | ListPreference listPreference = (ListPreference) preference;
54 | int index = listPreference.findIndexOfValue(stringValue);
55 |
56 | // Set the summary to reflect the new value.
57 | preference.setSummary(
58 | index >= 0
59 | ? listPreference.getEntries()[index]
60 | : null);
61 |
62 | } else if (preference instanceof RingtonePreference) {
63 | // For ringtone preferences, look up the correct display value
64 | // using RingtoneManager.
65 | if (TextUtils.isEmpty(stringValue)) {
66 | // Empty values correspond to 'silent' (no ringtone).
67 | preference.setSummary(R.string.pref_ringtone_silent);
68 |
69 | } else {
70 | Ringtone ringtone = RingtoneManager.getRingtone(
71 | preference.getContext(), Uri.parse(stringValue));
72 |
73 | if (ringtone == null) {
74 | // Clear the summary if there was a lookup error.
75 | preference.setSummary(null);
76 | } else {
77 | // Set the summary to reflect the new ringtone display
78 | // name.
79 | String name = ringtone.getTitle(preference.getContext());
80 | preference.setSummary(name);
81 | }
82 | }
83 |
84 | } else {
85 | // For all other preferences, set the summary to the value's
86 | // simple string representation.
87 | preference.setSummary(stringValue);
88 | }
89 | return true;
90 | }
91 | };
92 |
93 | /**
94 | * Helper method to determine if the device has an extra-large screen. For
95 | * example, 10" tablets are extra-large.
96 | */
97 | private static boolean isXLargeTablet(Context context) {
98 | return (context.getResources().getConfiguration().screenLayout
99 | & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
100 | }
101 |
102 | /**
103 | * Binds a preference's summary to its value. More specifically, when the
104 | * preference's value is changed, its summary (line of text below the
105 | * preference title) is updated to reflect the value. The summary is also
106 | * immediately updated upon calling this method. The exact display format is
107 | * dependent on the type of preference.
108 | *
109 | * @see #sBindPreferenceSummaryToValueListener
110 | */
111 | private static void bindPreferenceSummaryToValue(Preference preference) {
112 | // Set the listener to watch for value changes.
113 | preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
114 |
115 | // Trigger the listener immediately with the preference's
116 | // current value.
117 | sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
118 | PreferenceManager
119 | .getDefaultSharedPreferences(preference.getContext())
120 | .getString(preference.getKey(), ""));
121 | }
122 |
123 | @Override
124 | protected void onCreate(Bundle savedInstanceState) {
125 | super.onCreate(savedInstanceState);
126 | setupActionBar();
127 | }
128 |
129 | /**
130 | * Set up the {@link android.app.ActionBar}, if the API is available.
131 | */
132 | private void setupActionBar() {
133 | ActionBar actionBar = getSupportActionBar();
134 | if (actionBar != null) {
135 | // Show the Up button in the action bar.
136 | actionBar.setDisplayHomeAsUpEnabled(true);
137 | }
138 | }
139 |
140 | @Override
141 | public boolean onMenuItemSelected(int featureId, MenuItem item) {
142 | int id = item.getItemId();
143 | if (id == android.R.id.home) {
144 | if (!super.onMenuItemSelected(featureId, item)) {
145 | NavUtils.navigateUpFromSameTask(this);
146 | }
147 | return true;
148 | }
149 | return super.onMenuItemSelected(featureId, item);
150 | }
151 |
152 | /**
153 | * {@inheritDoc}
154 | */
155 | @Override
156 | public boolean onIsMultiPane() {
157 | return isXLargeTablet(this);
158 | }
159 |
160 | /**
161 | * {@inheritDoc}
162 | */
163 | @Override
164 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
165 | public void onBuildHeaders(List>() {
61 | @Override
62 | public void onChanged(@Nullable final List
> mAllAlarms;
21 |
22 | public AlarmRepository(Application application) {
23 | // Application is used instead of Context in order to prevent memory leaks
24 | // between Activity switches
25 | AlarmDatabase db = AlarmDatabase.getInstance(application);
26 | mAlarmModel = db.alarmModel();
27 | mAllAlarms = mAlarmModel.getAllAlarms();
28 | }
29 |
30 | // Observed LiveData will notify the observer when data has changed
31 | public LiveData
> getAllAlarms() {
32 | return mAllAlarms;
33 | }
34 |
35 | public void insert(Alarm alarm) {
36 | new insertAsyncTask(mAlarmModel).execute(alarm);
37 | }
38 |
39 | public void replace(Alarm alarm) {
40 | new replaceAsyncTask(mAlarmModel).execute(alarm);
41 | }
42 |
43 | public void update(Alarm alarm) {
44 | new updateAsyncTask(mAlarmModel).execute(alarm);
45 | }
46 |
47 | public void updateActive(Alarm alarm) {
48 | new updateActiveAsyncTask(mAlarmModel).execute(alarm);
49 | }
50 |
51 | public void delete(Alarm alarm) {
52 | new deleteAsyncTask(mAlarmModel).execute(alarm);
53 | }
54 |
55 | public Alarm getAlarmById(int id) throws ExecutionException, InterruptedException {
56 | return new getByIdAsyncTask(mAlarmModel).execute(id).get();
57 | }
58 |
59 |
60 | /*
61 | * Asynchronous Tasks
62 | *
63 | * One for each interaction with database.
64 | * All classes are static to prevent memory leaks.
65 | */
66 |
67 | private static class insertAsyncTask extends AsyncTask
> mAllAlarms;
25 |
26 | public AlarmViewModel(Application application) {
27 | super(application);
28 | mRepository = new AlarmRepository(application);
29 | mAllAlarms = mRepository.getAllAlarms();
30 | }
31 |
32 | // List is wrapped in LiveData in order to be observed and updated efficiently
33 | public LiveData
> getAllAlarms() {
34 | return mAllAlarms;
35 | }
36 |
37 | public void insert(Alarm alarm) {
38 | mRepository.insert(alarm);
39 | }
40 |
41 | public void replace(Alarm alarm) {
42 | mRepository.replace(alarm);
43 | }
44 |
45 | public void update(Alarm alarm) {
46 | mRepository.update(alarm);
47 | }
48 |
49 | public void updateActive(Alarm alarm) {
50 | mRepository.updateActive(alarm);
51 | }
52 |
53 | public void delete(Alarm alarm) {
54 | mRepository.delete(alarm);
55 | }
56 |
57 | public Alarm getAlarmById(int id) {
58 | try {
59 | return mRepository.getAlarmById(id);
60 | } catch (InterruptedException | ExecutionException e) {
61 | Log.e(TAG, "Error when retrieving alarm by id: " + id);
62 | e.printStackTrace();
63 | }
64 | return new Alarm();
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |