Java tutorial
/** * RxDroid - A Medication Reminder * Copyright (C) 2011-2014 Joseph Lehner <joseph.c.lehner@gmail.com> * * * RxDroid 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. Additional terms apply (see LICENSE). * * RxDroid 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 RxDroid. If not, see <http://www.gnu.org/licenses/>. * * */ package at.jclehner.rxdroid; import android.app.DatePickerDialog; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.support.v7.widget.PopupMenu; import android.text.SpannableString; import android.text.style.UnderlineSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.TouchDelegate; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.DatePicker; import android.widget.ImageView; import android.widget.Toast; import com.github.espiandev.showcaseview.ShowcaseView; import com.github.espiandev.showcaseview.ShowcaseViewBuilder2; import org.joda.time.LocalDate; import java.io.File; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; import at.jclehner.androidutils.LoaderListFragment; import at.jclehner.androidutils.RefString; import at.jclehner.rxdroid.db.Database; import at.jclehner.rxdroid.db.DatabaseHelper; import at.jclehner.rxdroid.db.DoseEvent; import at.jclehner.rxdroid.db.Drug; import at.jclehner.rxdroid.db.Entries; import at.jclehner.rxdroid.db.Entry; import at.jclehner.rxdroid.db.Patient; import at.jclehner.rxdroid.db.Schedule; import at.jclehner.rxdroid.ui.DialogLike; import at.jclehner.rxdroid.ui.ScheduleViewHolder; import at.jclehner.rxdroid.util.CollectionUtils; import at.jclehner.rxdroid.util.Components; import at.jclehner.rxdroid.util.Constants; import at.jclehner.rxdroid.util.DateTime; import at.jclehner.rxdroid.util.Extras; import at.jclehner.rxdroid.util.ShowcaseViews; import at.jclehner.rxdroid.util.Util; import at.jclehner.rxdroid.util.WrappedCheckedException; import at.jclehner.rxdroid.widget.DrugNameView; import at.jclehner.rxdroid.widget.DrugSupplyMonitor; public class DrugListActivity2 extends ActionBarActivity implements DialogLike.OnButtonClickListener { private static final String TAG = DrugListActivity2.class.getSimpleName(); public static final String EXTRA_DATE = "rxdroid:date"; public static final String EXTRA_STARTED_FROM_NOTIFICATION = "rxdroid:started_from_notification"; private boolean mIsShowingDbErrorDialog = false; @Override protected void onCreate(Bundle savedInstanceState) { Components.onCreateActivity(this, Components.NO_DATABASE_INIT); super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); final ActionBar ab = getSupportActionBar(); ab.setDisplayShowHomeEnabled(true); ab.setDisplayUseLogoEnabled(true); ab.setLogo(R.drawable.ic_logo_padded); if (Settings.getBoolean(Settings.Keys.IS_FIRST_LAUNCH, true)) { final Intent intent = new Intent(this, DoseTimePreferenceActivity2.class); intent.putExtra(DoseTimePreferenceActivity2.EXTRA_IS_FIRST_LAUNCH, true); startActivity(intent); finish(); } else if (savedInstanceState == null) initDrugListPagerFragment(); NotificationReceiver.rescheduleAlarmsAndUpdateNotification(true); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); } @Override protected void onResume() { super.onResume(); if (Version.SDK_IS_LOLLIPOP_OR_NEWER) { if (getIntent().getBooleanExtra(EXTRA_STARTED_FROM_NOTIFICATION, false)) { Log.i(TAG, "Rescheduling alarms; better safe than sorry!"); NotificationReceiver.rescheduleAlarmsAndUpdateNotification(true, false); getIntent().putExtra(EXTRA_STARTED_FROM_NOTIFICATION, false); } } } @Override public void onButtonClick(DialogLike dialogLike, int which) { if (which == DialogLike.BUTTON_NEGATIVE) { if (deleteDatabase()) { Toast.makeText(this, R.string._toast_db_reset_success, Toast.LENGTH_SHORT).show(); initDrugListPagerFragment(); } else Toast.makeText(this, R.string._toast_db_reset_failure, Toast.LENGTH_LONG).show(); } else finish(); } @Override public boolean onCreateOptionsMenu(Menu menu) { if (mIsShowingDbErrorDialog) { menu.add(R.string._title_restore).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { final Intent intent = new Intent(DrugListActivity2.this, BackupActivity.class); intent.putExtra(BackupActivity.EXTRA_NO_BACKUP_CREATION, true); startActivity(intent); return true; } }); } return super.onCreateOptionsMenu(menu); } protected void onLoaderException(RuntimeException e) { final DatabaseHelper.DatabaseError error; if (e instanceof WrappedCheckedException && ((WrappedCheckedException) e).getCauseType() == DatabaseHelper.DatabaseError.class) error = (DatabaseHelper.DatabaseError) e.getCause(); else if (e instanceof DatabaseHelper.DatabaseError) error = (DatabaseHelper.DatabaseError) e; else throw e; final StringBuilder sb = new StringBuilder(); sb.append(Util.getDbErrorMessage(this, error)); sb.append(" " + RefString.resolve(this, R.string._msg_db_error_footer)); final DialogLike dialog = new DialogLike(); dialog.setTitle(getString(R.string._title_error)); dialog.setMessage(sb); dialog.setNegativeButtonText(getString(R.string._btn_reset)); dialog.setPositiveButtonText(getString(R.string._btn_exit)); getSupportFragmentManager().beginTransaction().replace(android.R.id.content, dialog) .commitAllowingStateLoss(); mIsShowingDbErrorDialog = true; } private void initDrugListPagerFragment() { mIsShowingDbErrorDialog = false; supportInvalidateOptionsMenu(); final DrugListPagerFragment f = new DrugListPagerFragment(); final Bundle args = new Bundle(); args.putSerializable(DrugListFragment.ARG_DATE, getIntent().getSerializableExtra(EXTRA_DATE)); //args.putInt(DrugListFragment.ARG_PATIENT_ID, Patient.DEFAULT_PATIENT_ID); f.setArguments(args); f.setRetainInstance(false); getSupportFragmentManager().beginTransaction().replace(android.R.id.content, f, "pager").commit(); } private boolean deleteDatabase() { final String packageName = getApplicationInfo().packageName; final File dbDir = new File(Environment.getDataDirectory(), "data/" + packageName + "/databases"); final File currentDb = new File(dbDir, DatabaseHelper.DB_NAME); if (!dbDir.canWrite() || !currentDb.exists() || !currentDb.canWrite()) return false; return currentDb.delete(); } public static class DrugListPagerFragment extends Fragment implements DatePickerDialog.OnDateSetListener, NotificationReceiver.OnDoseTimeChangeListener, SystemEventReceiver.OnSystemTimeChangeListener { private class MyPagerAdapter extends FragmentStatePagerAdapter { public MyPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { final Date date = getDateForPage(position); return DrugListFragment.newInstance(date, mDtInfo.currentTime(), mPatientId, mShowingAll); } @Override public int getCount() { return ADAPTER_ITEMS; } @Override public int getItemPosition(Object object) { return POSITION_NONE; } }; public static final String ARG_PATIENT_ID = DrugListFragment.ARG_PATIENT_ID; public static final String ARG_DATE = DrugListFragment.ARG_DATE; public static final int ITEMS_PER_SIDE = 50; public static final int ADAPTER_ITEMS = 1 + 2 * ITEMS_PER_SIDE; public static final int CENTER_ITEM = 1 + ITEMS_PER_SIDE; public static final int OFFSCREEN_PAGES = 1; private ViewPager mPager; private int mPatientId; private Date mDisplayedDate; private Date mReferenceDate; private int mLastDoseTimePeriodIndex = -1; private boolean mShowingAll = false; private Settings.DoseTimeInfo mDtInfo; private boolean mUseDateArg = false; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setHasOptionsMenu(true); setMenuVisibility(true); final Bundle args = icicle != null ? icicle : getArguments(); mPatientId = args.getInt(ARG_PATIENT_ID); mLastDoseTimePeriodIndex = -1; if (icicle != null) mUseDateArg = icicle.getBoolean("use_date_arg"); else mUseDateArg = getArguments().getSerializable(ARG_DATE) != null; updateDoseTimeInfo(); Log.d(TAG, "onCreate: icicle=" + icicle); } @Override public void onResume() { super.onResume(); updateDoseTimeInfo(); Date date = null; boolean forceSetDate = true; if (mUseDateArg) date = (Date) getArguments().getSerializable(ARG_DATE); Log.d(TAG, "onResume: ARG_DATE => " + date); if (date == null) { date = mDtInfo.activeDate(); //forceSetDate = mLastDoseTimePeriodIndex != mDtInfo.doseTimePeriodIndex(); } mLastDoseTimePeriodIndex = mDtInfo.doseTimePeriodIndex(); setDate(date, forceSetDate); mUseDateArg = false; NotificationReceiver.registerOnDoseTimeChangeListener(this); SystemEventReceiver.registerOnSystemTimeChangeListener(this); } @Override public void onPause() { super.onPause(); NotificationReceiver.unregisterOnDoseTimeChangeListener(this); SystemEventReceiver.registerOnSystemTimeChangeListener(this); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean("use_date_arg", mUseDateArg); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.drug_list_fragment, menu); } @Override public void onPrepareOptionsMenu(Menu menu) { if (mDisplayedDate == null) return; final int dateCmp = mDisplayedDate.compareTo(mDtInfo.activeDate()); if (Settings.getBoolean(Settings.Keys.USE_SAFE_MODE, false)) menu.removeItem(R.id.menuitem_take_all); else menu.findItem(R.id.menuitem_take_all).setVisible(dateCmp == 0); menu.findItem(R.id.menuitem_date) .setTitle(dateCmp != 0 ? R.string._title_today : R.string._title_go_to_date); menu.findItem(R.id.menuitem_toggle_filtering) .setTitle(!mShowingAll ? R.string._title_show_all : R.string._title_filter); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menuitem_date: { if (mDtInfo.activeDate().equals(mDisplayedDate)) { // TODO set minDate depending on history size Util.createDatePickerDialog(getActivity(), new LocalDate(mDisplayedDate), this).show(); } else { setDate(mDtInfo.activeDate(), true); } return true; } case R.id.menuitem_patient: { } case R.id.menuitem_add: { Intent intent = new Intent(Intent.ACTION_INSERT); intent.setClass(getActivity(), DrugEditActivity2.class); startActivity(intent); return true; } case R.id.menuitem_preferences: { Intent intent = new Intent(); intent.setClass(getActivity(), SettingsActivity.class); startActivity(intent); return true; } case R.id.menuitem_toggle_filtering: { mShowingAll = !mShowingAll; setDate(mDisplayedDate, true); return true; } case R.id.menuitem_take_all: { Entries.markAllNotifiedDosesAsTaken(mPatientId); return true; } } return super.onOptionsItemSelected(item); } @Override public void onDateSet(DatePicker datePicker, int year, int month, int day) { setDate(DateTime.date(year, month, day), false); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.fragment_drug_list_pager, container, false); mPager = ((ViewPager) view.findViewById(R.id.pager)); mPager.setOffscreenPageLimit(OFFSCREEN_PAGES); mPager.setOnPageChangeListener(mPageListener); mPager.setAdapter(new MyPagerAdapter(getChildFragmentManager())); mPager.setPageMargin(Util.pixelsFromDips(getActivity(), 48)); return view; } @Override public void onDoseTimeBegin(Date date, int doseTime) { updateDoseTimeInfoAndSetToActiveDate(); } @Override public void onDoseTimeEnd(Date date, int doseTime) { updateDoseTimeInfoAndSetToActiveDate(); } @Override public void onTimeChanged(int type) { updateDoseTimeInfoAndSetToActiveDate(); } private void updateDoseTimeInfoAndSetToActiveDate() { updateDoseTimeInfo(); setDate(mDtInfo.activeDate(), true); } private void setDate(Date date, boolean force) { Log.d(TAG, "setDate: date=" + date + ", force=" + force); if (date == null) throw new NullPointerException(); if (!force && date.equals(mDisplayedDate)) return; mReferenceDate = mDisplayedDate = date; updateDoseTimeInfo(); mPager.getAdapter().notifyDataSetChanged(); mPager.setCurrentItem(CENTER_ITEM, false); updateActionBar(); } private void updateDoseTimeInfo() { mDtInfo = Settings.getDoseTimeInfo(); } private Date getDateForPage(int page) { return DateTime.add(mReferenceDate, Calendar.DAY_OF_MONTH, page - CENTER_ITEM); } private void updateActionBar() { getActivity().supportInvalidateOptionsMenu(); final SpannableString dateStr = new SpannableString(DateTime.toNativeDate(mDisplayedDate)); if (mDtInfo.activeDate().equals(mDisplayedDate)) Util.applyStyle(dateStr, new UnderlineSpan()); //Util.applyStyle(dateStr, new RelativeSizeSpan(0.75f)); ((ActionBarActivity) getActivity()).getSupportActionBar().setTitle(dateStr); } private final ViewPager.OnPageChangeListener mPageListener = new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int page) { if (mReferenceDate != null) { mDisplayedDate = getDateForPage(page); updateActionBar(); Log.d(TAG, "onPageSelected: page=" + page + ", date=" + mDisplayedDate); } } }; } public static class DrugListFragment extends LoaderListFragment<Drug> implements View.OnClickListener, View.OnLongClickListener, Database.OnChangeListener { static class Adapter extends LLFAdapter<Drug> { static class ViewHolder extends ScheduleViewHolder { DrugNameView name; ImageView icon; DrugSupplyMonitor supply; View history; View missedDoseIndicator; } private final Settings.DoseTimeInfo mDtInfo; public Adapter(DrugListFragment fragment) { super(fragment); mDtInfo = Settings.getDoseTimeInfo((Calendar) fragment.getArguments().getSerializable("time")); } @Override public View getView(int position, View view, ViewGroup viewGroup) { final ViewHolder holder; if (view == null) { view = mInflater.inflate(R.layout.drug_view, viewGroup, false); holder = new ViewHolder(); holder.icon = (ImageView) view.findViewById(R.id.drug_icon); holder.name = (DrugNameView) view.findViewById(R.id.drug_name); holder.supply = (DrugSupplyMonitor) view.findViewById(R.id.text_supply); holder.history = view.findViewById(R.id.frame_history_menu); holder.missedDoseIndicator = view.findViewById(R.id.img_missed_dose_warning); final View frameSupply = (View) holder.supply.getParent(); frameSupply.post(new Runnable() { @Override public void run() { final Rect rect = new Rect(0, 0, frameSupply.getWidth(), frameSupply.getHeight()); frameSupply.setTouchDelegate(new TouchDelegate(rect, holder.supply)); } }); holder.name.setOnClickListener((View.OnClickListener) mFragment); holder.history.setOnClickListener((View.OnClickListener) mFragment); holder.supply.setOnClickListener((View.OnClickListener) mFragment); holder.supply.setOnLongClickListener((View.OnLongClickListener) mFragment); holder.setDoseViewsAndDividersFromLayout(view); for (DoseView doseView : holder.doseViews) { doseView.setOnClickListener((View.OnClickListener) mFragment); doseView.setOnLongClickListener((View.OnLongClickListener) mFragment); //doseView.setOnCreateContextMenuListener(mFragment); } view.setTag(holder); } else holder = (ViewHolder) view.getTag(); final Loader.DrugWrapper wrapper = getItemHolder(position); holder.name.setDrug(wrapper.item); holder.name.setTag(wrapper.item.getId()); holder.icon.setImageResource(Util.getDrugIconDrawable(wrapper.item.getIcon())); holder.supply.setToday(mDtInfo.activeDate()); holder.supply.setDrugAndDate(wrapper.item, wrapper.date); holder.supply.setVisibility(wrapper.isSupplyVisible ? View.VISIBLE : View.INVISIBLE); holder.history.setTag(wrapper.item.getId()); holder.missedDoseIndicator.setVisibility( (wrapper.isActiveDate && wrapper.hasMissingDoses) ? View.VISIBLE : View.GONE); for (int i = 0; i != holder.doseViews.length; ++i) { final DoseView doseView = holder.doseViews[i]; if (!doseView.hasInfo(wrapper.date, wrapper.item)) doseView.setDoseFromDrugAndDate(wrapper.date, wrapper.item); doseView.setDimmed(wrapper.doseViewDimmed[i]); } final int dividerVisibility; if (view.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) dividerVisibility = View.GONE; else dividerVisibility = View.VISIBLE; for (View divider : holder.dividers) { if (divider != null) divider.setVisibility(dividerVisibility); } return view; } } static class Loader extends LLFLoader<Drug> { public static class DrugWrapper extends ItemHolder<Drug> { public DrugWrapper(Drug drug) { super(drug); } public Date date; public boolean isActiveDate; public boolean isSupplyLow; public boolean isSupplyVisible; public boolean hasMissingDoses; public boolean[] doseViewDimmed = { false, false, false, false }; } private final boolean mShowAll; private final int mPatientId; private final Date mDate; private final Settings.DoseTimeInfo mDtInfo; public Loader(Context context, Bundle args) { super(context); checkArgs(args); mShowAll = args.getBoolean(ARG_SHOW_ALL); mDate = (Date) args.getSerializable(ARG_DATE); mPatientId = args.getInt(ARG_PATIENT_ID); mDtInfo = Settings.getDoseTimeInfo((Calendar) args.getSerializable("time")); } @Override public List<DrugWrapper> doLoadInBackground() { Database.init(); final List<Drug> allDrugs = Entries.getAllDrugs(mPatientId); final List<Drug> drugs = mShowAll ? allDrugs : (List<Drug>) CollectionUtils.filter(allDrugs, mFilter); Collections.sort(drugs, mComparator); final ArrayList<DrugWrapper> data = new ArrayList<DrugWrapper>(drugs.size()); for (Drug drug : drugs) { final DrugWrapper wrapper = new DrugWrapper(drug); wrapper.date = mDate; wrapper.isActiveDate = mDate.equals(mDtInfo.activeDate()); wrapper.isSupplyLow = Entries.hasLowSupplies(drug, mDate); wrapper.hasMissingDoses = Entries.hasMissingDosesBeforeDate(drug, mDate); wrapper.isSupplyVisible = (drug.getRefillSize() != 0 /*|| !drug.getCurrentSupply().isZero()*/) && !mDate.before(mDtInfo.activeDate()); if (wrapper.isActiveDate) { // This will "underflow" if nextDoseTime is TIME_MORNING, but // this doesn't matter since it just dims all doses final int maxDoseTimeForNoDim = mDtInfo.nextDoseTime() - 1; if (maxDoseTimeForNoDim >= Schedule.TIME_MORNING && mDtInfo.activeDoseTime() != Schedule.TIME_INVALID) { for (int i = 0; i != wrapper.doseViewDimmed.length; ++i) { final int doseTime = Schedule.TIME_MORNING + i; if (doseTime <= maxDoseTimeForNoDim && !drug.getDose(doseTime, mDate).isZero()) wrapper.doseViewDimmed[i] = Entries.countDoseEvents(drug, mDate, doseTime) != 0; else wrapper.doseViewDimmed[i] = true; } } } else if (wrapper.date.after(mDtInfo.activeDate())) wrapper.doseViewDimmed = new boolean[] { true, true, true, true }; data.add(wrapper); } return data; } private final CollectionUtils.Filter<Drug> mFilter = new CollectionUtils.Filter<Drug>() { private final boolean skipAutoDoseEventDrugs = !Settings .getBoolean(Settings.Keys.SHOW_SUPPLY_MONITORS, false); @Override public boolean matches(Drug drug) { if (!drug.isActive() || (drug.hasAutoDoseEvents() && skipAutoDoseEventDrugs)) return false; if (Entries.countDoseEvents(drug, mDate, null) != 0) return true; if (mDtInfo.activeDate().equals(mDate)) { if (Entries.hasMissingDosesBeforeDate(drug, mDate)) return true; if (Entries.hasLowSupplies(drug, mDate)) return true; } if (!drug.hasDoseOnDate(mDate)) return false; return true; } }; private final Comparator<Drug> mComparator = new Comparator<Drug>() { @Override public int compare(Drug lhs, Drug rhs) { if (getSmartSortScore(lhs) < getSmartSortScore(rhs)) return -1; else return 1; } // lower score is better (higher up) private int getSmartSortScore(Drug drug) { if (!drug.isActive()) return 100000 - drug.getId(); int score = drug.hasAutoDoseEvents() ? 50000 : 0; if (!Entries.hasAllDoseEvents(drug, mDate, mDtInfo.activeOrNextDoseTime(), false)) score -= 5000; if (!drug.getDose(mDtInfo.activeOrNextDoseTime(), mDate).isZero()) { if (Entries.countDoseEvents(drug, mDate, mDtInfo.activeOrNextDoseTime()) == 0) score -= 3000; } if (Entries.hasLowSupplies(drug, mDate)) score -= 1000; if (mDtInfo.activeDate().equals(mDate)) { if (Entries.hasMissingDosesBeforeDate(drug, mDate)) score -= 1000; } if (drug.hasDoseOnDate(mDate)) score -= 2500; return score; } }; } public static String ARG_DATE = "date"; public static String ARG_TIME = "time"; public static String ARG_PATIENT_ID = "patient_id"; public static String ARG_SHOW_ALL = "show_all"; private Date mDate; public static DrugListFragment newInstance(Date date, Calendar time, int patientId, boolean showAll) { final Bundle args = new Bundle(); args.putSerializable(ARG_DATE, date); args.putSerializable(ARG_TIME, time); args.putInt(ARG_PATIENT_ID, patientId); args.putBoolean(ARG_SHOW_ALL, showAll); final DrugListFragment instance = new DrugListFragment(); instance.setArguments(args); instance.setRetainInstance(false); return instance; } @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setHasOptionsMenu(true); mDate = (Date) getArguments().getSerializable(ARG_DATE); } @Override public void onResume() { super.onResume(); Database.registerEventListener(this); } @Override public void onPause() { super.onPause(); Database.unregisterEventListener(this); } @Override public void onClick(View view) { if (view.getId() == R.id.frame_history_menu) { Intent intent = new Intent(getActivity(), DoseHistoryActivity.class); intent.putExtra(Extras.DRUG_ID, (Integer) view.getTag()); startActivity(intent); } else if (view instanceof DoseView) { if (BuildConfig.DEBUG) { try { final LocalDate fragmentDate = new LocalDate(mDate); final LocalDate activityDate = LocalDate.parse(((ActionBarActivity) getActivity()) .getSupportActionBar().getTitle().toString().replace('/', '-')); if (!fragmentDate.equals(activityDate)) { Toast.makeText(getActivity(), "fragmentDate=" + fragmentDate + "\nactivityDate=" + activityDate, Toast.LENGTH_LONG).show(); Log.d(TAG, "Date mismatch:\n fragmentDate=" + fragmentDate + "\n activityDate=" + activityDate); } } catch (RuntimeException e) { Log.w(TAG, e); } } showDoseDialog((DoseView) view, false); } else if (view.getId() == R.id.drug_name) { Intent intent = new Intent(getActivity(), DrugEditActivity2.class); intent.setAction(Intent.ACTION_EDIT); intent.putExtra(DrugEditActivity2.EXTRA_DRUG_ID, (Integer) view.getTag()); startActivity(intent); } else if (view instanceof DrugSupplyMonitor) { final Drug drug = ((DrugSupplyMonitor) view).getDrug(); if (drug != null) { //final Date today = DateTime.today(); final int daysLeft = Entries.getSupplyDaysLeftForDrug(drug, mDate); final String dateString = DateTime .toNativeDate(DateTime.add(mDate, Calendar.DAY_OF_MONTH, daysLeft)); Toast.makeText(getActivity(), getString(R.string._toast_low_supplies, dateString), Toast.LENGTH_LONG).show(); } } } @Override public boolean onLongClick(View view) { if (view instanceof DrugSupplyMonitor) { final Drug drug = ((DrugSupplyMonitor) view).getDrug(); if (drug != null) { final DrugSupplyEditFragment dialog = DrugSupplyEditFragment.newInstance(drug); dialog.show(getFragmentManager(), "supply_edit_dialog"); } } else if (view instanceof DoseView) { final PopupMenu pm = new PopupMenu(getActivity(), view); pm.inflate(R.menu.dose_view_context_menu); final Menu menu = pm.getMenu(); final DoseView doseView = (DoseView) view; final Drug drug = doseView.getDrug(); final int doseTime = doseView.getDoseTime(); if (doseView.getDose().isZero()) { menu.removeItem(R.id.menuitem_skip); menu.removeItem(R.id.menuitem_remove_dose); } else if (doseView.wasDoseTaken()) menu.removeItem(R.id.menuitem_skip); else menu.removeItem(R.id.menuitem_remove_dose); pm.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem menuItem) { if (menuItem.getItemId() == R.id.menuitem_remove_dose) { final Fraction.MutableFraction dose = new Fraction.MutableFraction(); for (DoseEvent intake : Entries.findDoseEvents(drug, mDate, doseTime)) { dose.add(intake.getDose()); Database.delete(intake); } if (drug.getRefillSize() != 0) { drug.setCurrentSupply(drug.getCurrentSupply().plus(dose)); Database.update(drug); } } else if (menuItem.getItemId() == R.id.menuitem_take) { // When using the popup menu, force the dialog to display // even when disabled in settings. showDoseDialog(doseView, Settings.getBoolean(Settings.Keys.SKIP_DOSE_DIALOG, true)); } else if (menuItem.getItemId() == R.id.menuitem_skip) Database.create(new DoseEvent(drug, doseView.getDate(), doseTime)); else if (menuItem.getItemId() == R.id.menuitem_edit) { final Intent intent = new Intent(getActivity(), DrugEditActivity2.class); intent.setAction(Intent.ACTION_EDIT); intent.putExtra(DrugEditActivity2.EXTRA_DRUG_ID, drug.getId()); startActivity(intent); } else return false; return true; } }); pm.show(); doseView.setBackgroundColor(Theme.getColorAttribute(R.attr.colorControlHighlight)); pm.setOnDismissListener(new PopupMenu.OnDismissListener() { @Override public void onDismiss(PopupMenu popupMenu) { //doseView.setConstantBackgroundResource(0); //doseView.setForceSelected(false); doseView.setBackgroundResource(Theme.getResourceAttribute(R.attr.selectableItemBackground)); } }); } return true; } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { if (menu == null) return; menu.add(R.string._title_help).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { showHelpOverlaysIfApplicable(true); return true; } }); } @Override protected void onLoaderException(final RuntimeException e) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { ((DrugListActivity2) getActivity()).onLoaderException(e); } }); } @Override protected LLFAdapter<Drug> onCreateAdapter() { return new Adapter(this); } @Override protected LLFLoader<Drug> onCreateLoader() { return new Loader(getActivity(), getArguments()); } private void reloadLoader() { getLoaderManager().restartLoader(0, null, this); } @Override public void onLoadFinished(List<LLFLoader.ItemHolder<Drug>> data) { super.onLoadFinished(data); setEmptyText(getEmptyText()); showHelpOverlaysIfApplicable(false); } private void showDoseDialog(DoseView doseView, boolean force) { final Bundle args = new Bundle(); args.putInt(DoseDialog.ARG_DRUG_ID, doseView.getDrug().getId()); args.putInt(DoseDialog.ARG_DOSE_TIME, doseView.getDoseTime()); args.putSerializable(DoseDialog.ARG_DATE, doseView.getDate()); args.putBoolean(DoseDialog.ARG_FORCE_SHOW, force); final DoseDialog dialog = new DoseDialog(getActivity()); dialog.setArgs(args); dialog.show(); } private void showHelpOverlaysIfApplicable(boolean force) { final List<Drug> drugs = Entries .getAllDrugs(getArguments().getInt(ARG_PATIENT_ID, Patient.DEFAULT_PATIENT_ID)); final Settings.DoseTimeInfo dtInfo = Settings .getDoseTimeInfo((Calendar) getArguments().getSerializable(ARG_TIME)); // "date_swipe" is used for historic reasons if (force || (drugs.size() == 1 && !Settings.wasDisplayedOnce("date_swipe"))) { Date date = dtInfo.activeDate(); final Drug drug; if (force) { final DoseView dv = (DoseView) getActivity().findViewById(R.id.morning); if (dv == null) return; drug = dv.getDrug(); } else { drug = drugs.get(0); while (!drug.hasDoseOnDate(date)) { date = DateTime.add(date, Calendar.DAY_OF_MONTH, 1); //Log.i(TAG, "Trying date " + DateTime.toDateString(date) + " for " + drug); //Log.i(TAG, "Trying date " + DateTime.toDateString(date) + " for " + drug); } final Fragment f = getFragmentManager().findFragmentByTag("pager"); if (f instanceof DrugListPagerFragment) ((DrugListPagerFragment) f).setDate(date, false); } int doseToHighlight = R.id.noon; for (int i = 0; i != Constants.DOSE_TIMES.length; ++i) { if (!drug.getDose(Schedule.TIME_MORNING + i, date).isZero()) { doseToHighlight = Constants.DOSE_VIEW_IDS[i]; break; } } final ShowcaseViews svs = new ShowcaseViews(); // 1. Swipe date ShowcaseViewBuilder2 svb = new ShowcaseViewBuilder2(getActivity()); svb.setText(R.string._help_title_swipe_date, R.string._help_msg_swipe_date); /*svb.setShotType(ShowcaseView.TYPE_ONE_SHOT); svb.setShowcaseId(0xdeadbeef + 0); svb.setShowcaseItem(ShowcaseView.ITEM_TITLE, 0, getActivity()); final DisplayMetrics metrics = new DisplayMetrics(); getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics); final float w = metrics.widthPixels; final float h = metrics.heightPixels; final float y = h * 0.6f; svb.setAnimatedGesture(-100, y, w, y);*/ svs.add(tag(svb.build(), "Swipe date")); // 2. Edit drug svb = new ShowcaseViewBuilder2(getActivity()); svb.setText(R.string._help_title_edit_drug, R.string._help_msg_edit_drug); //svb.setShotType(ShowcaseView.TYPE_ONE_SHOT); //svb.setShowcaseId(0xdeadbeef + 1); svb.setShowcaseView(R.id.drug_icon, getActivity()); svs.add(tag(svb.build(false), "Edit drug")); // 3. Take dose & long press svb = new ShowcaseViewBuilder2(getActivity()); svb.setText(R.string._help_title_click_dose, R.string._help_msg_click_dose); //svb.setShotType(ShowcaseView.TYPE_ONE_SHOT); //svb.setShowcaseId(0xdeadbeef + 4); svb.setShowcaseView(doseToHighlight, getActivity()); svs.add(tag(svb.build(false), "Take dose & long press")); Settings.setDisplayedOnce("date_swipe"); svs.show(); } } private ShowcaseView tag(ShowcaseView sv, String tag) { if (sv != null) sv.setTag(tag); return sv; } private String getEmptyText() { if (Database.countAll(Drug.class) == 0) { final boolean hasHardwareMenuKey; if (Version.SDK_IS_PRE_HONEYCOMB) hasHardwareMenuKey = true; else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { // For Honeycomb, there appears to be no way to find out. As it // targets tablets only, we will assume that none of these have a // hardware menu key... hasHardwareMenuKey = false; } else { hasHardwareMenuKey = ViewConfiguration.get(getActivity()).hasPermanentMenuKey(); } final StringBuilder sb = new StringBuilder( RefString.resolve(getActivity(), R.string._msg_no_drugs_compact_ab)); if (hasHardwareMenuKey) sb.append(" " + getString(R.string._help_msg_menu_hardware)); else sb.append(" " + getString(R.string._help_msg_menu_ab_overflow)); return sb.toString(); } else return getString(R.string._msg_no_doses_on_this_day); } private static void checkArgs(Bundle args) { if (!args.containsKey(ARG_DATE) || !args.containsKey(ARG_PATIENT_ID) || !args.containsKey(ARG_TIME)) throw new IllegalArgumentException("args=" + args.keySet()); } @Override public void onEntryCreated(Entry entry, int flags) { if (entry instanceof Drug) reloadLoader(); else if (entry instanceof DoseEvent) getActivity().supportInvalidateOptionsMenu(); } @Override public void onEntryDeleted(Entry entry, int flags) { if (entry instanceof Drug) reloadLoader(); } @Override public void onEntryUpdated(Entry entry, int flags) { reloadLoader(); } } }