Java tutorial
/* * Calendula - An assistant for personal medication management. * Copyright (C) 2016 CITIUS - USC * * Calendula is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software. If not, see <http://www.gnu.org/licenses/>. */ package es.usc.citius.servando.calendula.activities; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.ColorStateList; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CollapsingToolbarLayout; import android.support.design.widget.FloatingActionButton; import android.support.v4.util.Pair; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.format.DateUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewAnimationUtils; import android.view.ViewGroup; import android.view.ViewPropertyAnimator; import android.view.animation.Animation; import android.view.animation.DecelerateInterpolator; import android.view.animation.OvershootInterpolator; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.mikepenz.community_material_typeface_library.CommunityMaterial; import com.mikepenz.iconics.IconicsDrawable; import org.joda.time.DateTime; import org.joda.time.Interval; import org.joda.time.LocalDate; import org.joda.time.LocalTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import es.usc.citius.servando.calendula.CalendulaActivity; import es.usc.citius.servando.calendula.CalendulaApp; import es.usc.citius.servando.calendula.HomePagerActivity; import es.usc.citius.servando.calendula.R; import es.usc.citius.servando.calendula.database.DB; import es.usc.citius.servando.calendula.persistence.DailyScheduleItem; import es.usc.citius.servando.calendula.persistence.Medicine; import es.usc.citius.servando.calendula.persistence.Patient; import es.usc.citius.servando.calendula.persistence.Presentation; import es.usc.citius.servando.calendula.persistence.Routine; import es.usc.citius.servando.calendula.persistence.Schedule; import es.usc.citius.servando.calendula.persistence.ScheduleItem; import es.usc.citius.servando.calendula.scheduling.AlarmIntentParams; import es.usc.citius.servando.calendula.scheduling.AlarmScheduler; import es.usc.citius.servando.calendula.util.AvatarMgr; import es.usc.citius.servando.calendula.util.IconUtils; import es.usc.citius.servando.calendula.util.ScreenUtils; import es.usc.citius.servando.calendula.util.Snack; import es.usc.citius.servando.calendula.util.view.ArcTranslateAnimation; public class ConfirmActivity extends CalendulaActivity { public static final int DEFAULT_CHECK_MARGIN = 3; private static final String TAG = "ConfirmActivity"; boolean isRoutine; boolean stateChanged = false; int position = -1; int color; Patient patient; Routine routine; Schedule schedule; LocalTime time; LocalDate date; RecyclerView listView; ImageView avatar; TextView title; ImageView avatarTitle; TextView titleTitle; TextView hour; TextView minute; TextView friendlyTime; TextView takeMadsMessage; IconicsDrawable uncheckedIcon; IconicsDrawable checkedIcon; FloatingActionButton fab; String action; AppBarLayout appBarLayout; ConfirmItemAdapter itemAdapter; CollapsingToolbarLayout toolbarLayout; View toolbarTitle; List<DailyScheduleItem> items = new ArrayList<>(); DateTimeFormatter timeFormatter = DateTimeFormat.forPattern("kk:mm"); DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("dd/MM/YYYY"); boolean isToday; boolean isInWindow; boolean isDistant; View chekAllOverlay; ImageView checkAllImage; String relativeTime = ""; private boolean fromNotification = false; /* * Returns the intake margin interval */ public Pair<DateTime, DateTime> getCheckMarginInterval(DateTime intakeTime) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); String checkMarginStr = prefs.getString("check_window_margin", "" + DEFAULT_CHECK_MARGIN); int checkMargin = Integer.parseInt(checkMarginStr); DateTime start = intakeTime.minusMinutes(30); DateTime end = intakeTime.plusHours(checkMargin); return new Pair<>(start, end); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.confirm, menu); MenuItem item = menu.findItem(R.id.action_delay); if (!isInWindow) { item.setVisible(false); } else { item.setIcon(new IconicsDrawable(this).icon(CommunityMaterial.Icon.cmd_history).color(Color.WHITE) .sizeDp(24)); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { // Respond to the action bar's Up/Home button case android.R.id.home: if (fromNotification) { startActivity(new Intent(this, HomePagerActivity.class)); finish(); } else { supportFinishAfterTransition(); } return true; case R.id.action_delay: showDelayDialog(); return true; } return super.onOptionsItemSelected(item); } public void showDelayDialog() { final int[] values = this.getResources().getIntArray(R.array.delays_array_values); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.notification_delay).setItems(R.array.delays_array, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { int minutes = values[which]; if (isRoutine) { AlarmScheduler.instance().onUserDelayRoutine(routine, date, ConfirmActivity.this, minutes); } else { AlarmScheduler.instance().onUserDelayHourlySchedule(schedule, time, date, ConfirmActivity.this, minutes); } String msg = ConfirmActivity.this.getString(R.string.alarm_delayed_message, minutes); Toast.makeText(ConfirmActivity.this, msg, Toast.LENGTH_SHORT).show(); supportFinishAfterTransition(); } }); builder.create().show(); } public String getDisplayableDose(String dose, Medicine m) { return dose + " " + m.presentation().units(getResources()); } public void showEnsureConfirmDialog(final DialogInterface.OnClickListener listener, boolean uncheck) { AlertDialog.Builder builder = new AlertDialog.Builder(this); DateTime t = date.toDateTime(time); String title = t.isAfterNow() ? getString(R.string.intake_not_available) : getString(R.string.meds_from) + " " + date.toString("EEEE dd") + " " + getString(R.string.at_time_connector) + " " + time.toString(timeFormatter); String msg = t.isAfterNow() ? getString(R.string.confirm_future_intake_warning, relativeTime) : uncheck ? getString(R.string.unconfirm_past_intake_warning, relativeTime) : getString(R.string.confirm_past_intake_warning); builder.setMessage(msg).setCancelable(true) .setIcon(IconUtils.icon(this, CommunityMaterial.Icon.cmd_history, R.color.black, 36)) .setTitle(title); if (t.isAfterNow()) { builder.setNegativeButton(getString(R.string.tutorial_understood), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); } else { builder.setPositiveButton( uncheck ? getString(R.string.meds_unconfirm_ok) : getString(R.string.meds_confirm_ok), listener) .setNegativeButton( uncheck ? getString(R.string.meds_unconfirm_cancel) : getString(R.string.meds_confirm_cancel), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); } AlertDialog alert = builder.create(); alert.show(); } @Override public void onBackPressed() { if (fromNotification) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { finishAndRemoveTask(); } else { finish(); } } else { super.onBackPressed(); } } void onClickFab() { boolean somethingChecked = false; for (DailyScheduleItem item : items) { if (!item.takenToday()) { item.setTakenToday(true); item.save(); somethingChecked = true; } } if (somethingChecked) { itemAdapter.notifyDataSetChanged(); stateChanged = true; fab.postDelayed(new Runnable() { @Override public void run() { animateAllChecked(); } }, 100); onAllChecked(); } else { supportFinishAfterTransition(); } } void moveArrowsDown(int duration) { checkAllImage.animate().translationY(ScreenUtils.dpToPx(getResources(), 150f)).setDuration(duration) .setInterpolator(new OvershootInterpolator()).start(); } void showRippleByApi(int x, int y) { int duration = 500; int arrowDuration = 400; chekAllOverlay.postDelayed(new Runnable() { @Override public void run() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { finishAndRemoveTask(); } else { finish(); } } }, duration + 300); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { showRipple(x, y, duration); } else { chekAllOverlay.setVisibility(View.VISIBLE); chekAllOverlay.animate().alpha(1).setDuration(duration).start(); } moveArrowsDown(arrowDuration); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); processIntent(); setContentView(R.layout.activity_confirm); isToday = LocalDate.now().equals(date); isInWindow = AlarmScheduler.isWithinDefaultMargins(date.toDateTime(time), this); DateTime dt = date.toDateTime(time); DateTime now = DateTime.now(); Pair<DateTime, DateTime> interval = getCheckMarginInterval(dt); isDistant = !new Interval(interval.first, interval.second).contains(now); color = AvatarMgr.colorsFor(getResources(), patient.avatar())[0]; color = Color.parseColor("#263238"); setupStatusBar(Color.TRANSPARENT); setupToolbar("", Color.TRANSPARENT, Color.WHITE); toolbar.setTitleTextColor(Color.WHITE); findViewById(R.id.imageView5).setBackgroundColor(patient.color()); fab = (FloatingActionButton) findViewById(R.id.myFAB); listView = (RecyclerView) findViewById(R.id.listView); avatar = (ImageView) findViewById(R.id.patient_avatar); title = (TextView) findViewById(R.id.routine_name); takeMadsMessage = (TextView) findViewById(R.id.textView3); chekAllOverlay = findViewById(R.id.check_overlay); checkAllImage = (ImageView) findViewById(R.id.check_all_image); avatarTitle = (ImageView) findViewById(R.id.patient_avatar_title); titleTitle = (TextView) findViewById(R.id.routine_name_title); friendlyTime = (TextView) findViewById(R.id.user_friendly_time); hour = (TextView) findViewById(R.id.routines_list_item_hour); minute = (TextView) findViewById(R.id.routines_list_item_minute); toolbarTitle = findViewById(R.id.toolbar_title); avatar.setImageResource(AvatarMgr.res(patient.avatar())); avatarTitle.setImageResource(AvatarMgr.res(patient.avatar())); titleTitle.setText(patient.name()); title.setText((isRoutine ? routine.name() : schedule.toReadableString(this))); takeMadsMessage.setText(isInWindow ? getString(R.string.agenda_zoom_meds_time) : getString(R.string.meds_from) + " " + date.toString("EEEE dd")); relativeTime = DateUtils.getRelativeTimeSpanString(dt.getMillis(), now.getMillis(), 5 * DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_ALL).toString(); hour.setText(time.toString("kk:")); minute.setText(time.toString("mm")); friendlyTime.setText(relativeTime.substring(0, 1).toUpperCase() + relativeTime.substring(1)); if (isDistant) { fab.setBackgroundTintList(ColorStateList.valueOf(getResources().getColor(R.color.android_orange_dark))); } fab.setImageDrawable(new IconicsDrawable(this).icon(CommunityMaterial.Icon.cmd_check_all).color(Color.WHITE) .sizeDp(24).paddingDp(0)); checkAllImage.setImageDrawable(new IconicsDrawable(this).icon(CommunityMaterial.Icon.cmd_check_all) .color(Color.WHITE).sizeDp(100).paddingDp(0)); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { boolean somethingToCheck = false; for (DailyScheduleItem item : items) { if (!item.takenToday()) { somethingToCheck = true; break; } } if (somethingToCheck) { if (isDistant) { showEnsureConfirmDialog(new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { onClickFab(); } }, false); } else { onClickFab(); } } else { Snack.show(getResources().getString(R.string.all_meds_taken), ConfirmActivity.this); } } }); appBarLayout = (AppBarLayout) findViewById(R.id.appbar); toolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar); toolbarLayout.setContentScrimColor(patient.color()); setupListView(); if ("delay".equals(action)) { if (isRoutine && routine != null) { ReminderNotification.cancel(this, ReminderNotification.routineNotificationId(routine.getId().intValue())); } else if (schedule != null) { ReminderNotification.cancel(this, ReminderNotification.scheduleNotificationId(schedule.getId().intValue())); } showDelayDialog(); } } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); // finish and restart with the new params finish(); startActivity(intent); } @Override protected void onResume() { super.onResume(); //Toast.makeText(this, "Is distant: " + isDistant + " (" + date.toDateTime(time).toString("dd/MM/YYYY, kk:mm")+")", Toast.LENGTH_LONG).show(); } protected void onDailyAgendaItemCheck(final ImageButton v) { int total = items.size(); int checked = 0; for (DailyScheduleItem i : items) { if (i.takenToday()) checked++; } if (checked == total) { onAllChecked(); } else { if (isRoutine) { AlarmScheduler.instance().onDelayRoutine(routine, date, ConfirmActivity.this); } else { AlarmScheduler.instance().onDelayHourlySchedule(schedule, time, date, ConfirmActivity.this); } } } @Override protected void onDestroy() { if (stateChanged) { CalendulaApp.eventBus().post(new ConfirmStateChangeEvent(position)); } super.onDestroy(); } private void animateAllChecked() { int width = appBarLayout.getWidth(); int middle = width / 2; int fabCentered = middle - fab.getWidth() / 2; int translationX = (int) fab.getX() - fabCentered; int translationY = ScreenUtils.dpToPx(getResources(), 150); final int rippleX = middle; final int rippleY = (int) (fab.getY() + fab.getHeight() / 2) - translationY; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Animation arcAnimation = new ArcTranslateAnimation(0, -translationX, 0, -translationY); arcAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { showRippleByApi(rippleX, rippleY); } @Override public void onAnimationRepeat(Animation animation) { } }); arcAnimation.setInterpolator(new DecelerateInterpolator()); arcAnimation.setDuration(200); arcAnimation.setFillAfter(true); fab.startAnimation(arcAnimation); } else { ViewPropertyAnimator animator = fab.animate().translationX(-translationX).setDuration(300); animator.setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { showRippleByApi(rippleX, rippleY); } }); animator.start(); } } private void showRipple(int x, int y, int duration) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { Log.d(TAG, "Ripple x,y [" + x + ", " + y + "]"); chekAllOverlay.setVisibility(View.INVISIBLE); // get the final radius for the clipping circle int finalRadius = (int) Math.hypot(chekAllOverlay.getWidth(), chekAllOverlay.getHeight()); // create the animator for this view (the start radius is zero) Animator anim = ViewAnimationUtils.createCircularReveal(chekAllOverlay, x, y, fab.getWidth() / 2, finalRadius); anim.setInterpolator(new DecelerateInterpolator()); // make the view visible and start the animation chekAllOverlay.setVisibility(View.VISIBLE); anim.setDuration(duration).start(); } } private void setupListView() { loadItems(); itemAdapter = new ConfirmItemAdapter(); LinearLayoutManager llm = new LinearLayoutManager(this); listView.setLayoutManager(llm); listView.setAdapter(itemAdapter); listView.setItemAnimator(new DefaultItemAnimator()); } private void processIntent() { Intent i = getIntent(); Long routineId = i.getLongExtra("routine_id", -1); Long scheduleId = i.getLongExtra("schedule_id", -1); String dateStr = i.getStringExtra("date"); String timeStr = i.getStringExtra("schedule_time"); String actionType = i.getIntExtra("actionType", AlarmIntentParams.AUTO) == AlarmIntentParams.USER ? "user" : "auto"; action = i.getStringExtra("action"); position = i.getIntExtra("position", -1); fromNotification = position == -1; if (dateStr != null) { date = LocalDate.parse(dateStr, dateFormatter); } else { // this should never happen, but, just in case, redirect to home and show error Intent intent = new Intent(this, HomePagerActivity.class); intent.putExtra("invalid_notification_error", true); startActivity(intent); finish(); } Log.d("Confirm", timeStr + ", " + dateStr + ", " + routineId + ", " + scheduleId + ", " + date); if (routineId != -1) { isRoutine = true; routine = Routine.findById(routineId); time = routine.time(); patient = routine.patient(); } else { time = LocalTime.parse(timeStr, timeFormatter); schedule = Schedule.findById(scheduleId); patient = schedule.patient(); } } private void loadItems() { if (isRoutine) { List<ScheduleItem> rsi = routine.scheduleItems(); Log.d("Confirm", rsi.size() + " items"); for (ScheduleItem si : rsi) { DailyScheduleItem item = DB.dailyScheduleItems().findByScheduleItemAndDate(si, date); if (item != null) items.add(item); } } else { items.add(DB.dailyScheduleItems().findBy(schedule, date, time)); } for (DailyScheduleItem i : items) { Log.d("Confirm", i != null ? i.toString() : "Null"); } } private void onAllChecked() { if (isRoutine) { AlarmScheduler.instance().onIntakeCompleted(routine, date, this); } else { AlarmScheduler.instance().onIntakeCompleted(schedule, time, date, this); } } private Drawable getCheckedIcon(int color) { if (checkedIcon == null) { checkedIcon = new IconicsDrawable(this, CommunityMaterial.Icon.cmd_checkbox_marked_circle_outline) //cmd_checkbox_marked_outline .sizeDp(30).paddingDp(0).color(color); } return checkedIcon; } private Drawable getUncheckedIcon(int color) { if (uncheckedIcon == null) { uncheckedIcon = new IconicsDrawable(this, CommunityMaterial.Icon.cmd_checkbox_blank_circle_outline) //cmd_checkbox_blank_outline .sizeDp(30).paddingDp(0).color(color); } return uncheckedIcon; } public static class ConfirmStateChangeEvent { public int position = -1; public ConfirmStateChangeEvent(int position) { this.position = position; } } private class ConfirmItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { ConfirmItemViewHolder h; DailyScheduleItem i; ScheduleItem si; Long sid; Schedule s; Medicine m; Presentation p; @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.confirm_activity_list_item, parent, false); return new ConfirmItemViewHolder(v); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { h = (ConfirmItemViewHolder) holder; i = items.get(position); si = i.scheduleItem(); sid = i.boundToSchedule() ? i.schedule().getId() : si.schedule().getId(); s = DB.schedules().findById(sid); m = s.medicine(); p = m.presentation(); String status = getString(R.string.med_not_taken); if (i.timeTaken() != null) { status = (i.takenToday() ? getString(R.string.med_taken_at) : getString(R.string.med_cancelled_at)) + " " + i.timeTaken().toString("kk:mm") + "h"; } h.med.setText(m.name()); h.dose.setText(getDisplayableDose(i.boundToSchedule() ? s.displayDose() : si.displayDose(), m)); h.status.setText(status); h.dailyScheduleItem = i; updateCheckedStatus(); } @Override public int getItemCount() { return items.size(); } void updateCheckedStatus() { Drawable medDrawable = new IconicsDrawable(ConfirmActivity.this).icon(p.icon()) .color(i.takenToday() ? Color.parseColor("#81c784") : Color.parseColor("#11000000")).sizeDp(36) .paddingDp(0); Drawable checkDrawable = i.takenToday() ? getCheckedIcon(Color.parseColor("#81c784")) : getUncheckedIcon(Color.parseColor("#11000000")); h.check.setImageDrawable(checkDrawable); h.icon.setImageDrawable(medDrawable); } public class ConfirmItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { TextView med; TextView dose; TextView status; ImageButton check; ImageView icon; DailyScheduleItem dailyScheduleItem; public ConfirmItemViewHolder(View itemView) { super(itemView); med = (TextView) itemView.findViewById(R.id.med_item_name); dose = (TextView) itemView.findViewById(R.id.med_item_dose); status = (TextView) itemView.findViewById(R.id.med_item_status); check = (ImageButton) itemView.findViewById(R.id.check_button); icon = (ImageView) itemView.findViewById(R.id.imageView); itemView.setOnClickListener(this); check.setOnClickListener(this); } @Override public void onClick(View view) { final boolean taken = dailyScheduleItem.takenToday(); if (isDistant) { showEnsureConfirmDialog(new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dailyScheduleItem.setTakenToday(!taken); dailyScheduleItem.save(); stateChanged = true; onDailyAgendaItemCheck(check); notifyItemChanged(getAdapterPosition()); } }, taken); } else { dailyScheduleItem.setTakenToday(!taken); dailyScheduleItem.save(); stateChanged = true; onDailyAgendaItemCheck(check); notifyItemChanged(getAdapterPosition()); } } } } }