package org.sana.android.fragment;
import java.io.FileNotFoundException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
import org.joda.time.DateTime;
import org.sana.R;
import org.sana.android.Constants;
import org.sana.android.app.Locales;
import org.sana.android.app.Preferences;
import org.sana.android.content.Intents;
import org.sana.android.content.core.PatientWrapper;
import org.sana.android.provider.Patients;
import org.sana.android.provider.Patients.Contract;
import org.sana.android.provider.Subjects;
import org.sana.android.util.Bitmaps;
import org.sana.android.util.Dates;
import org.sana.android.util.Logf;
import org.sana.android.widget.ScrollCompleteListener;
import org.sana.core.Patient;
import org.sana.util.DateUtil;
import org.sana.util.StringUtil;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.support.v4.widget.SimpleCursorAdapter;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AlphabetIndexer;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SectionIndexer;
import android.widget.TextView;
/**
* Fragment displaying all patients.
*
* @author Sana Development Team
*/
public class PatientListFragment extends ListFragment implements LoaderCallbacks<Cursor>, ScrollCompleteListener {
public static final String TAG = PatientListFragment.class.getSimpleName();
private static final int PATIENTS_LOADER = 0;
static final String[] mProjection = new String[] {
Contract._ID,
Contract.GIVEN_NAME,
Contract.FAMILY_NAME,
Contract.PATIENT_ID,
Contract.LOCATION,
Contract.IMAGE,
Contract.DOB
};
private Uri mUri;
private PatientCursorAdapter mAdapter;
private OnPatientSelectedListener mListener;
Handler mHandler;
private boolean doSync = false;
private int delta =1000*60;
private ScrollCompleteListener mScrollListener = null;
//
// Activity Methods
//
/** {@inheritDoc} */
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate()");
super.onCreate(savedInstanceState);
//setRetainInstance(true);
Locales.updateLocale(this.getActivity(), getString(R.string.force_locale));
delta = getResources().getInteger(R.integer.sync_delta_subjects);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
Log.d(TAG, "onActivityCreated()");
super.onActivityCreated(savedInstanceState);
// signal the dispatcher to sync
mUri = getActivity().getIntent().getData();
if (mUri == null) {
mUri = Patients.CONTENT_URI;
}
Log.d(TAG, "onActivityCreated(): sync?");
mAdapter = new PatientCursorAdapter(getActivity(), null, 0);
setListAdapter(mAdapter);
mAdapter.setOnScrollCompleteListener(this);
// Do we sync with server
delta = getActivity().getResources().getInteger(R.integer.sync_delta_subjects);
//sync(getActivity(), Subjects.CONTENT_URI);
LoaderManager.enableDebugLogging(true);
getActivity().getSupportLoaderManager().initLoader(PATIENTS_LOADER, null, this);
}
/** {@inheritDoc} */
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
if (mListener != null) {
mListener.onPatientSelected(id);
}
}
//
// Loader Callbacks
//
/** {@inheritDoc} */
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle bundle) {
Log.d(TAG, "onCreateLoader() ");
CursorLoader loader = new CursorLoader(getActivity(),
mUri,
mProjection,
null, null, Patients.GIVEN_NAME_SORT_ORDER);
return loader;
}
/** {@inheritDoc} */
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
Log.d(TAG, "onLoadFinished() ");
if (cursor == null || (cursor !=null && cursor.getCount() == 0)) {
setEmptyText(getString(R.string.msg_no_patients));
}
if(cursor != null)
//mAdapter.swapCursor(cursor);
((PatientCursorAdapter) this.getListAdapter()).swapCursor(cursor);
}
/*
* (non-Javadoc)
* @see android.support.v4.app.LoaderManager.LoaderCallbacks#onLoaderReset(android.support.v4.content.Loader)
*/
@Override
public void onLoaderReset(Loader<Cursor> loader) {
Log.d(TAG, "onLoaderReset() ");
//mAdapter.swapCursor(null);
((PatientCursorAdapter) this.getListAdapter()).swapCursor(null);
}
/**
* Events specific to this PatientListFragment
*
* @author Sana Development Team
*/
public interface OnPatientSelectedListener {
/**
* Callback when a patient is selected in the list.
*
* @param patientId The selected patient's ID.
*/
public void onPatientSelected(long patientId);
}
/**
* Sets a listener to this fragment.
*
* @param listener
*/
public void setOnPatientSelectedListener(OnPatientSelectedListener listener) {
mListener = listener;
}
static class ViewHolder{
Patient patient;
ImageView image;
TextView name;
TextView systemId;
TextView location;
TextView label;
int position = 1;
}
/**
* Adapter for patient information
*
* @author Sana Development Team
*/
public static class PatientCursorAdapter extends CursorAdapter implements SectionIndexer{
//private final Activity context;
private int[] mRowStates = new int[0];
private AlphabetIndexer mAlphaIndexer = null;
private final LayoutInflater mInflater;
private static final int STATE_UNKNOWN = 0;
private static final int STATE_LABELED = 1;
private static final int STATE_UNLABELED = 2;
private static final String ALPHABET = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private final String mAlphabet;
private String dateFormat = null;
private SimpleDateFormat sdf;
private ScrollCompleteListener mScrollListener = null;
private String[] months;
public PatientCursorAdapter(Context context, Cursor c) {
super(context.getApplicationContext(),c,false);
mInflater = LayoutInflater.from(context);
dateFormat = context.getString(R.string.display_date_format);
sdf = new SimpleDateFormat(dateFormat);
mAlphabet = " " + mContext.getString(R.string.cfg_alphabet);
String locale = Preferences.getString(context, Constants.PREFERENCE_LOCALE, "en");
months = context.getResources().getStringArray(R.array.months_long_format);
init(c);
}
public PatientCursorAdapter(Context context) {
this(context, null, 0);
}
public PatientCursorAdapter(Context context, Cursor c, int flags) {
super(context,c, flags);
mInflater = LayoutInflater.from(context);
dateFormat = context.getString(R.string.display_date_format);
sdf = new SimpleDateFormat(dateFormat);
mAlphabet = " " + mContext.getString(R.string.cfg_alphabet);
String locale = Preferences.getString(context, Constants.PREFERENCE_LOCALE, "en");
Locales.updateLocale(context, locale);
months = context.getResources().getStringArray(R.array.months_long_format);
init(c);
}
private void init(Cursor c) {
if (c == null) {
return;
}
//mWrapper = new PatientWrapper(c);
//c.setNotificationUri(context.getContentResolver(), Patients.CONTENT_URI);
mRowStates = new int[c.getCount()];
Arrays.fill(mRowStates, STATE_UNKNOWN);
if(mRowStates.length > 0)
mRowStates[0] = STATE_LABELED;
mAlphaIndexer = new AlphabetIndexer(c,
1,
mAlphabet);
mAlphaIndexer.setCursor(c);
}
public Cursor index(Cursor cursor){
if(cursor != null){
mRowStates = new int[cursor.getCount()];
mAlphaIndexer = new AlphabetIndexer(cursor,
1,
mAlphabet);
mAlphaIndexer.setCursor(cursor);
Arrays.fill(mRowStates, STATE_UNKNOWN);
} else {
mRowStates = new int[0];
mAlphaIndexer = null;
}
if(mRowStates.length > 0)
mRowStates[0] = STATE_LABELED;
return cursor;
}
@Override
public void changeCursor (Cursor cursor){
Log.d(TAG+".mAdapter", "changeCursor(Cursor)");
index(cursor);
super.changeCursor(cursor);
}
@Override
public Cursor swapCursor(Cursor newCursor) {
Log.i(TAG + ".mAdapter", "swapCursor(Cursor)");
index(newCursor);
return super.swapCursor(newCursor);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
Log.d(TAG+".mAdapter", "bindView(): cursor position: " + ((cursor != null)? cursor.getPosition(): 0));
int position = this.getCursor().getPosition();
// Set patient name and image
ImageView image = (ImageView)view.findViewById(R.id.image);
String imagePath = ((Cursor) this.getItem(position)).getString(5);
//image.setImageResource(R.drawable.unknown);
if(imagePath != null){
try {
image.setImageBitmap(Bitmaps.decodeSampledBitmapFromFile(
Uri.parse(imagePath).getPath(), 128, 128));
} catch(java.io.FileNotFoundException e){
Log.e(TAG, e.getMessage());
image.setImageResource(R.drawable.ic_contact_picture);
} catch (Exception e){
Log.e(TAG, e.getMessage());
image.setImageResource(R.drawable.ic_contact_picture);
}
}
String familyName = ((Cursor) getItem(position)).getString(2);
String givenName = ((Cursor) getItem(position)).getString(1);
String displayName = StringUtil.formatPatientDisplayName(givenName, familyName);
TextView name = (TextView) view.findViewById(R.id.name);
name.setText(displayName);
TextView systemId = (TextView)view.findViewById(R.id.system_id);
String id = ((Cursor) this.getItem(position)).getString(3);
//String id = mWrapper.getStringField(Contract.PATIENT_ID);
systemId.setText((TextUtils.isEmpty(id)? "000000":id));
TextView dobView = (TextView)view.findViewById(R.id.dob);
String dobStr = ((Cursor) this.getItem(position)).getString(6);
String localDobStr = null;
Date dob = null;
try {
localDobStr = this.getDateStringFromSQL(dobStr);
} catch (Exception e) {
e.printStackTrace();
}
//String id = mWrapper.getStringField(Contract.PATIENT_ID);
dobView.setText((TextUtils.isEmpty(localDobStr)? dobStr:
localDobStr));
TextView location = (TextView)view.findViewById(R.id.location);
String locationVal = ((Cursor) this.getItem(position)).getString(4);
location.setText(locationVal);
// Alphabet divider
boolean needsSeparator = false;
// pos is 0 based array index,
int pos = ((Cursor) getItem(position)).getPosition();
char currentSectionLabel = getSectionLabel(displayName);
Log.d(TAG, "...Checking if needs row separator label. " +
"position="+pos);
switch (mRowStates[pos]) {
case STATE_LABELED:
needsSeparator = true;
break;
case STATE_UNLABELED:
needsSeparator = false;
break;
case STATE_UNKNOWN:
default:
// First cell always needs to be sectioned
if (pos == 0) {
needsSeparator = true;
mRowStates[pos] = STATE_LABELED;
} else {
char prevSectionLabel = getSectionLabel(
formatName(((Cursor) getItem(position -1))));
Log.d(TAG,"...prev section=" + prevSectionLabel +", " +
"current section=" + currentSectionLabel);
if (prevSectionLabel != currentSectionLabel) {
needsSeparator = true;
mRowStates[pos] = STATE_LABELED;
} else {
needsSeparator = false;
mRowStates[pos] = STATE_UNLABELED;
}
((Cursor) this.getItem(position)).moveToPosition(pos);
}
break;
}
if (needsSeparator) {
Log.d(TAG, "...adding separator");
view.findViewById(R.id.header).setVisibility(View.VISIBLE);
TextView label = (TextView) view.findViewById(R.id.txt_section);
label.setText(("" + currentSectionLabel).toUpperCase(Locale.getDefault()));
} else {
Log.d(TAG, "...hiding separator");
view.findViewById(R.id.header).setVisibility(View.GONE);
}
// Handle scroll complete when we bind last item in cursor
Log.d(TAG, "...cursor count=" + cursor.getCount());
Log.d(TAG, "...cursor position=" + cursor.getPosition());
if(cursor.isLast() || cursor.getPosition() >= (cursor.getCount()*0.8)){
if(mScrollListener != null)
mScrollListener.onScrollComplete();
}
}
public View getView(int position, View convertView, ViewGroup parent) {
Log.d(TAG+".mAdapter", "get view ");
return super.getView(position,convertView, parent);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
Log.d(TAG+".mAdapter", "new view cursor position: " + ((cursor != null)? cursor.getPosition(): 0));
View view = mInflater.inflate(R.layout.patient_list_item, null);
//bindView(view, context, cursor);
return view;
}
@Override
public int getPositionForSection(int sectionIndex) {
if(mAlphaIndexer == null)
return 0;
return mAlphaIndexer.getPositionForSection(sectionIndex);
}
@Override
public int getSectionForPosition(int position) {
if(mAlphaIndexer == null)
return 0;
return mAlphaIndexer.getSectionForPosition(position);
}
@Override
public Object[] getSections()
{
if(mAlphaIndexer == null)
return null;
return mAlphaIndexer.getSections();
}
public String formatName(Cursor cursor){
String givenName = cursor.getString(cursor.getColumnIndex(Contract
.GIVEN_NAME));
String familyName = cursor.getString(cursor.getColumnIndex(Contract
.FAMILY_NAME));
String displayName = StringUtil.formatPatientDisplayName(givenName,
familyName);
return displayName;
}
public char getSectionLabel(String str){
str = str.trim();
if (!TextUtils.isEmpty(str)) {
str = str.substring(0, 1).toLowerCase(Locale.getDefault());
} else {
str = " ";
}
return str.charAt(0);
}
public String getDateStringFromSQL(String date) throws ParseException {
Date d = Dates.fromSQL(date);
DateTime dt = new DateTime(d);
int month = dt.getMonthOfYear();
int dayOfMonth = dt.getDayOfMonth();
int year = dt.getYear();
String localizedMonth = months[month - 1];
return String.format("%02d %s %04d", dayOfMonth, localizedMonth, year);
}
public String getDateString(Date date) {
return sdf.format(date);
}
public void setOnScrollCompleteListener(ScrollCompleteListener listener){
mScrollListener = listener;
}
}
public final boolean sync(Context context, Uri uri){
Log.d(TAG, "sync(Context,Uri)");
boolean result = false;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
long lastSync = prefs.getLong("patient_sync", 0);
long now = System.currentTimeMillis();
Log.d(TAG, "last: " + lastSync +", now: " + now+ ", delta: " + (now-lastSync) + ", doSync: " + ((now - lastSync) > 86400000));
// TODO
// Once a day 86400000
if((now - lastSync) > delta){
Logf.W(TAG, "sync(): synchronizing patient list");
prefs.edit().putLong("patient_sync", now).commit();
Intent intent = new Intent(Intents.ACTION_READ,uri);
context.startService(intent);
result = true;
}
return result;
}
public final boolean syncForced(Context context, Uri uri){
Log.d(TAG, "syncForced(Context,Uri)");
boolean result = false;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
long now = System.currentTimeMillis();
// Once a day 86400000
prefs.edit().putLong("patient_sync", now).commit();
Intent intent = new Intent(Intents.ACTION_READ,uri);
context.startService(intent);
result = true;
return result;
}
public void setOnScrollCompleteListener(ScrollCompleteListener listener){
mScrollListener = listener;
}
public final void onScrollComplete(){
Log.i(TAG, "onScrollComplete()");
if(mScrollListener != null)
mScrollListener.onScrollComplete();
}
}