/* * Copyright 2019 LinkedIn Corporation * All Rights Reserved. * * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for * license information. */ package com.linkedin.android.tachyon.sample; import android.app.DatePickerDialog; import android.app.TimePickerDialog; import android.content.DialogInterface; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.DatePicker; import android.widget.RadioButton; import android.widget.ScrollView; import android.widget.TextView; import android.widget.TimePicker; import com.linkedin.android.tachyon.DayView; import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Locale; import androidx.annotation.ColorRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.collection.LongSparseArray; /** * This sample activity demonstrates how to populate the day view with events. */ public class SampleActivity extends AppCompatActivity { /** * Some examples to demonstrate how the day view renders multiple events that are in close * proximity to each other. */ @NonNull private static final Event[] INITIAL_EVENTS = { new Event("Walk the dog", "Park", 0, 0, 30, android.R.color.holo_red_dark), new Event("Meeting", "Office", 1, 30, 90, android.R.color.holo_purple), new Event("Phone call", "555-5555", 2, 0, 45, android.R.color.holo_orange_dark), new Event("Lunch", "Cafeteria", 2, 30, 30, android.R.color.holo_green_dark), new Event("Dinner", "Home", 18, 0, 30, android.R.color.holo_green_dark)}; private Calendar day; private LongSparseArray<List<Event>> allEvents; private DateFormat dateFormat; private DateFormat timeFormat; private Calendar editEventDate; private Calendar editEventStartTime; private Calendar editEventEndTime; private Event editEventDraft; private ViewGroup content; private TextView dateTextView; private ScrollView scrollView; private DayView dayView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Create a new calendar object set to the start of today day = Calendar.getInstance(); day.set(Calendar.HOUR_OF_DAY, 0); day.set(Calendar.MINUTE, 0); day.set(Calendar.SECOND, 0); day.set(Calendar.MILLISECOND, 0); // Populate today's entry in the map with a list of example events allEvents = new LongSparseArray<>(); allEvents.put(day.getTimeInMillis(), new ArrayList<>(Arrays.asList(INITIAL_EVENTS))); dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()); timeFormat = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()); setContentView(R.layout.sample_activity); content = findViewById(R.id.sample_content); dateTextView = findViewById(R.id.sample_date); scrollView = findViewById(R.id.sample_scroll); dayView = findViewById(R.id.sample_day); // Inflate a label view for each hour the day view will display Calendar hour = (Calendar) day.clone(); List<View> hourLabelViews = new ArrayList<>(); for (int i = dayView.getStartHour(); i <= dayView.getEndHour(); i++) { hour.set(Calendar.HOUR_OF_DAY, i); TextView hourLabelView = (TextView) getLayoutInflater().inflate(R.layout.hour_label, dayView, false); hourLabelView.setText(timeFormat.format(hour.getTime())); hourLabelViews.add(hourLabelView); } dayView.setHourLabelViews(hourLabelViews); onDayChange(); } public void onPreviousClick(View v) { day.add(Calendar.DAY_OF_YEAR, -1); onDayChange(); } public void onNextClick(View v) { day.add(Calendar.DAY_OF_YEAR, 1); onDayChange(); } public void onAddEventClick(View v) { editEventDate = (Calendar) day.clone(); editEventStartTime = (Calendar) day.clone(); editEventEndTime = (Calendar) day.clone(); editEventEndTime.add(Calendar.MINUTE, 30); showEditEventDialog(false, null, null, android.R.color.holo_red_dark); } public void onScrollClick(View v) { showScrollTargetDialog(); } private void onDayChange() { dateTextView.setText(dateFormat.format(day.getTime())); onEventsChange(); } private void onEventsChange() { // The day view needs a list of event views and a corresponding list of event time ranges List<View> eventViews = null; List<DayView.EventTimeRange> eventTimeRanges = null; List<Event> events = allEvents.get(day.getTimeInMillis()); if (events != null) { // Sort the events by start time so the layout happens in correct order Collections.sort(events, new Comparator<Event>() { @Override public int compare(Event o1, Event o2) { return o1.hour < o2.hour ? -1 : (o1.hour == o2.hour ? (o1.minute < o2.minute ? -1 : (o1.minute == o2.minute ? 0 : 1)) : 1); } }); eventViews = new ArrayList<>(); eventTimeRanges = new ArrayList<>(); // Reclaim all of the existing event views so we can reuse them if needed, this process // can be useful if your day view is hosted in a recycler view for example List<View> recycled = dayView.removeEventViews(); int remaining = recycled != null ? recycled.size() : 0; for (final Event event : events) { // Try to recycle an existing event view if there are enough left, otherwise inflate // a new one View eventView = remaining > 0 ? recycled.get(--remaining) : getLayoutInflater().inflate(R.layout.event, dayView, false); ((TextView) eventView.findViewById(R.id.event_title)).setText(event.title); ((TextView) eventView.findViewById(R.id.event_location)).setText(event.location); eventView.setBackgroundColor(getResources().getColor(event.color)); // When an event is clicked, start a new draft event and show the edit event dialog eventView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { editEventDraft = event; editEventDate = (Calendar) day.clone(); editEventStartTime = Calendar.getInstance(); editEventStartTime.set(Calendar.HOUR_OF_DAY, editEventDraft.hour); editEventStartTime.set(Calendar.MINUTE, editEventDraft.minute); editEventStartTime.set(Calendar.SECOND, 0); editEventStartTime.set(Calendar.MILLISECOND, 0); editEventEndTime = (Calendar) editEventStartTime.clone(); editEventEndTime.add(Calendar.MINUTE, editEventDraft.duration); showEditEventDialog(true, editEventDraft.title, editEventDraft.location, editEventDraft.color); } }); eventViews.add(eventView); // The day view needs the event time ranges in the start minute/end minute format, // so calculate those here int startMinute = 60 * event.hour + event.minute; int endMinute = startMinute + event.duration; eventTimeRanges.add(new DayView.EventTimeRange(startMinute, endMinute)); } } // Update the day view with the new events dayView.setEventViews(eventViews, eventTimeRanges); } private void showEditEventDialog(boolean eventExists, @Nullable String eventTitle, @Nullable String eventLocation, @ColorRes int eventColor) { View view = getLayoutInflater().inflate(R.layout.edit_event_dialog, content, false); final TextView titleTextView = view.findViewById(R.id.edit_event_title); final TextView locationTextView = view.findViewById(R.id.edit_event_location); final Button dateButton = view.findViewById(R.id.edit_event_date); final Button startTimeButton = view.findViewById(R.id.edit_event_start_time); final Button endTimeButton = view.findViewById(R.id.edit_event_end_time); final RadioButton redRadioButton = view.findViewById(R.id.edit_event_red); final RadioButton blueRadioButton = view.findViewById(R.id.edit_event_blue); final RadioButton orangeRadioButton = view.findViewById(R.id.edit_event_orange); final RadioButton greenRadioButton = view.findViewById(R.id.edit_event_green); final RadioButton purpleRadioButton = view.findViewById(R.id.edit_event_purple); titleTextView.setText(eventTitle); locationTextView.setText(eventLocation); dateButton.setText(dateFormat.format(editEventDate.getTime())); dateButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { DatePickerDialog.OnDateSetListener listener = new DatePickerDialog.OnDateSetListener() { @Override public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) { editEventDate.set(Calendar.YEAR, year); editEventDate.set(Calendar.MONTH, month); editEventDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); dateButton.setText(dateFormat.format(editEventDate.getTime())); } }; new DatePickerDialog(SampleActivity.this, listener, day.get(Calendar.YEAR), day.get(Calendar.MONTH), day.get(Calendar.DAY_OF_MONTH)).show(); } }); startTimeButton.setText(timeFormat.format(editEventStartTime.getTime())); startTimeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { TimePickerDialog.OnTimeSetListener listener = new TimePickerDialog.OnTimeSetListener() { @Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) { editEventStartTime.set(Calendar.HOUR_OF_DAY, hourOfDay); editEventStartTime.set(Calendar.MINUTE, minute); startTimeButton.setText(timeFormat.format(editEventStartTime.getTime())); if (!editEventEndTime.after(editEventStartTime)) { editEventEndTime = (Calendar) editEventStartTime.clone(); editEventEndTime.add(Calendar.MINUTE, 30); endTimeButton.setText(timeFormat.format(editEventEndTime.getTime())); } } }; new TimePickerDialog(SampleActivity.this, listener, editEventStartTime.get(Calendar.HOUR_OF_DAY), editEventStartTime.get(Calendar.MINUTE), android.text.format.DateFormat.is24HourFormat(SampleActivity.this)).show(); } }); endTimeButton.setText(timeFormat.format(editEventEndTime.getTime())); endTimeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { TimePickerDialog.OnTimeSetListener listener = new TimePickerDialog.OnTimeSetListener() { @Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) { editEventEndTime.set(Calendar.HOUR_OF_DAY, hourOfDay); editEventEndTime.set(Calendar.MINUTE, minute); if (!editEventEndTime.after(editEventStartTime)) { editEventEndTime = (Calendar) editEventStartTime.clone(); editEventEndTime.add(Calendar.MINUTE, 30); } endTimeButton.setText(timeFormat.format(editEventEndTime.getTime())); } }; new TimePickerDialog(SampleActivity.this, listener, editEventEndTime.get(Calendar.HOUR_OF_DAY), editEventEndTime.get(Calendar.MINUTE), android.text.format.DateFormat.is24HourFormat(SampleActivity.this)).show(); } }); if (eventColor == android.R.color.holo_blue_dark) { blueRadioButton.setChecked(true); } else if (eventColor == android.R.color.holo_orange_dark) { orangeRadioButton.setChecked(true); } else if (eventColor == android.R.color.holo_green_dark) { greenRadioButton.setChecked(true); } else if (eventColor == android.R.color.holo_purple) { purpleRadioButton.setChecked(true); } else { redRadioButton.setChecked(true); } AlertDialog.Builder builder = new AlertDialog.Builder(this); // If the event already exists, we are editing it, otherwise we are adding a new event builder.setTitle(eventExists ? R.string.edit_event : R.string.add_event); // When the event changes are confirmed, read the new values from the dialog and then add // this event to the list builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { List<Event> events = allEvents.get(editEventDate.getTimeInMillis()); if (events == null) { events = new ArrayList<>(); allEvents.put(editEventDate.getTimeInMillis(), events); } String title = titleTextView.getText().toString(); String location = locationTextView.getText().toString(); int hour = editEventStartTime.get(Calendar.HOUR_OF_DAY); int minute = editEventStartTime.get(Calendar.MINUTE); int duration = (int) (editEventEndTime.getTimeInMillis() - editEventStartTime.getTimeInMillis()) / 60000; @ColorRes int color; if (blueRadioButton.isChecked()) { color = android.R.color.holo_blue_dark; } else if (orangeRadioButton.isChecked()) { color = android.R.color.holo_orange_dark; } else if (greenRadioButton.isChecked()) { color = android.R.color.holo_green_dark; } else if (purpleRadioButton.isChecked()) { color = android.R.color.holo_purple; } else { color = android.R.color.holo_red_dark; } events.add(new Event(title, location, hour, minute, duration, color)); onEditEventDismiss(true); } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { onEditEventDismiss(false); } }); // If the event already exists, provide a delete option if (eventExists) { builder.setNeutralButton(R.string.edit_event_delete, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { onEditEventDismiss(true); } }); } builder.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { onEditEventDismiss(false); } }); builder.setView(view); builder.show(); } private void showScrollTargetDialog() { View view = getLayoutInflater().inflate(R.layout.scroll_target_dialog, content, false); final Button timeButton = view.findViewById(R.id.scroll_target_time); final Button firstEventTopButton = view.findViewById(R.id.scroll_target_first_event_top); final Button firstEventBottomButton = view.findViewById(R.id.scroll_target_first_event_bottom); final Button lastEventTopButton = view.findViewById(R.id.scroll_target_last_event_top); final Button lastEventBottomButton = view.findViewById(R.id.scroll_target_last_event_bottom); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.scroll_to); builder.setNegativeButton(android.R.string.cancel, null); builder.setView(view); final AlertDialog dialog = builder.show(); timeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { TimePickerDialog.OnTimeSetListener listener = new TimePickerDialog.OnTimeSetListener() { @Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) { int top = dayView.getHourTop(hourOfDay); int bottom = dayView.getHourBottom(hourOfDay); int y = top + (bottom - top) * minute / 60; scrollView.smoothScrollTo(0, y); dialog.dismiss(); } }; new TimePickerDialog(SampleActivity.this, listener, 0, 0, android.text.format.DateFormat.is24HourFormat(SampleActivity.this)).show(); } }); firstEventTopButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { scrollView.smoothScrollTo(0, dayView.getFirstEventTop()); dialog.dismiss(); } }); firstEventBottomButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { scrollView.smoothScrollTo(0, dayView.getFirstEventBottom()); dialog.dismiss(); } }); lastEventTopButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { scrollView.smoothScrollTo(0, dayView.getLastEventTop()); dialog.dismiss(); } }); lastEventBottomButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { scrollView.smoothScrollTo(0, dayView.getLastEventBottom()); dialog.dismiss(); } }); } private void onEditEventDismiss(boolean modified) { if (modified && editEventDraft != null) { List<Event> events = allEvents.get(day.getTimeInMillis()); if (events != null) { events.remove(editEventDraft); } } editEventDraft = null; onEventsChange(); } /** * A data class used to represent an event on the calendar. */ private static class Event { @Nullable private final String title; @Nullable private final String location; private final int hour; private final int minute; private final int duration; @ColorRes private final int color; private Event(@Nullable String title, @Nullable String location, int hour, int minute, int duration, @ColorRes int color) { this.title = title; this.location = location; this.hour = hour; this.minute = minute; this.duration = duration; this.color = color; } } }