Rollcall: Fixed SWADroid allows multiple simultaneous submissions of the attendance list (#263)

* Fix #262

* Updated CHANGELOG

* Rollcall: Fixed The default image is not displayed if the user does not have a photo
This commit is contained in:
Juan Miguel Boyero Corral 2019-11-05 20:53:15 +01:00 committed by GitHub
parent 91e2043058
commit f045974e6e
6 changed files with 272 additions and 230 deletions

View File

@ -50,196 +50,197 @@ import es.ugr.swad.swadroid.modules.courses.Courses;
* @author Juan Miguel Boyero Corral <juanmi1982@gmail.com>
*/
public class Rollcall extends MenuExpandableListActivity implements
SwipeRefreshLayout.OnRefreshListener {
SwipeRefreshLayout.OnRefreshListener {
/**
* Rollcall tag name for Logcat
*/
private static final String TAG = Constants.APP_TAG + " Rollcall";
/**
* ListView of events
*/
private static ListView lvEvents;
/**
* Adapter for ListView of events
*/
private static EventsCursorAdapter adapter;
private final RefreshAdapterHandler mHandler = new RefreshAdapterHandler(this);
private final Runnable mRunnable = new Runnable() {
@Override
public void run() {
/**
* Rollcall tag name for Logcat
*/
private static final String TAG = Constants.APP_TAG + " Rollcall";
/**
* ListView of events
*/
private ListView lvEvents;
/**
* Adapter for ListView of events
*/
private static EventsCursorAdapter adapter;
private final RefreshAdapterHandler mHandler = new RefreshAdapterHandler(this);
private final Runnable mRunnable = new Runnable() {
@Override
public void run() {
/*
Database cursor for Adapter of events
*/
Cursor dbCursor = dbHelper.getEventsCourseCursor(Courses.getSelectedCourseCode());
startManagingCursor(dbCursor);
Cursor dbCursor = dbHelper.getEventsCourseCursor(Courses.getSelectedCourseCode());
startManagingCursor(dbCursor);
/*
* If there aren't events to show, hide the events lvEvents
* and show the empty events message
*/
if ((dbCursor == null) || (dbCursor.getCount() == 0)) {
Log.d(TAG, "Events list is empty");
/*
* If there aren't events to show, hide the events lvEvents
* and show the empty events message
*/
if ((dbCursor == null) || (dbCursor.getCount() == 0)) {
Log.d(TAG, "Events list is empty");
emptyEventsTextView.setText(R.string.eventsEmptyListMsg);
emptyEventsTextView.setVisibility(View.VISIBLE);
emptyEventsTextView.setText(R.string.eventsEmptyListMsg);
emptyEventsTextView.setVisibility(View.VISIBLE);
lvEvents.setVisibility(View.GONE);
} else {
Log.d(TAG, "Events list is not empty");
lvEvents.setVisibility(View.GONE);
} else {
Log.d(TAG, "Events list is not empty");
emptyEventsTextView.setVisibility(View.GONE);
lvEvents.setVisibility(View.VISIBLE);
}
emptyEventsTextView.setVisibility(View.GONE);
lvEvents.setVisibility(View.VISIBLE);
}
adapter = new EventsCursorAdapter(getBaseContext(), dbCursor, dbHelper);
lvEvents.setAdapter(adapter);
adapter = new EventsCursorAdapter(getBaseContext(), dbCursor, dbHelper);
lvEvents.setAdapter(adapter);
mProgressScreen.hide();
}
};
/**
* TextView for the empty events message
*/
private TextView emptyEventsTextView;
/**
* Layout with "Pull to refresh" function
*/
private SwipeRefreshLayout refreshLayout;
/**
* Progress screen
*/
private ProgressScreen mProgressScreen;
/**
* ListView click listener
*/
private ListView.OnItemClickListener clickListener = new ListView.OnItemClickListener() {
mProgressScreen.hide();
}
};
/**
* TextView for the empty events message
*/
private TextView emptyEventsTextView;
/**
* Layout with "Pull to refresh" function
*/
private SwipeRefreshLayout refreshLayout;
/**
* Progress screen
*/
private ProgressScreen mProgressScreen;
/**
* ListView click listener
*/
private ListView.OnItemClickListener clickListener = new ListView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent activity = new Intent(getApplicationContext(),
UsersActivity.class);
activity.putExtra("attendanceEventCode",
(int) adapter.getItemId(position));
startActivity(activity);
}
};
/* (non-Javadoc)
* @see es.ugr.swad.swadroid.MenuExpandableListActivity#onCreate(android.os.Bundle)
*/
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent activity = new Intent(getApplicationContext(),
UsersActivity.class);
activity.putExtra("attendanceEventCode",
(int) adapter.getItemId(position));
startActivity(activity);
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list_items_pulltorefresh);
refreshLayout = findViewById(R.id.swipe_container_list);
emptyEventsTextView = findViewById(R.id.list_item_title);
View mProgressScreenView = findViewById(R.id.progress_screen);
mProgressScreen = new ProgressScreen(mProgressScreenView, refreshLayout,
getString(R.string.loadingMsg), this);
lvEvents = findViewById(R.id.list_pulltorefresh);
lvEvents.setOnItemClickListener(clickListener);
lvEvents.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
}
@Override
public void onScroll(AbsListView absListView, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
boolean enable = true;
if ((lvEvents != null) && (lvEvents.getChildCount() > 0)) {
// check if the first item of the list is visible
boolean firstItemVisible = lvEvents.getFirstVisiblePosition() == 0;
// check if the top of the first item is visible
boolean topOfFirstItemVisible = lvEvents.getChildAt(0).getTop() == 0;
// enabling or disabling the refresh layout
enable = firstItemVisible && topOfFirstItemVisible;
}
refreshLayout.setEnabled(enable);
}
});
refreshLayout.setOnRefreshListener(this);
setAppearance();
getSupportActionBar().setSubtitle(Courses.getSelectedCourseShortName());
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
};
/* (non-Javadoc)
* @see es.ugr.swad.swadroid.MenuExpandableListActivity#onCreate(android.os.Bundle)
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list_items_pulltorefresh);
/* (non-Javadoc)
* @see es.ugr.swad.swadroid.MenuExpandableListActivity#Override(android.os.Bundle)
*/
@Override
protected void onStart() {
super.onStart();
refreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container_list);
emptyEventsTextView = (TextView) findViewById(R.id.list_item_title);
View mProgressScreenView = findViewById(R.id.progress_screen);
mProgressScreen = new ProgressScreen(mProgressScreenView, refreshLayout,
getString(R.string.loadingMsg), this);
lvEvents = (ListView) findViewById(R.id.list_pulltorefresh);
lvEvents.setOnItemClickListener(clickListener);
lvEvents.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
}
@Override
public void onScroll(AbsListView absListView, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
boolean enable = true;
if ((lvEvents != null) && (lvEvents.getChildCount() > 0)) {
// check if the first item of the list is visible
boolean firstItemVisible = lvEvents.getFirstVisiblePosition() == 0;
// check if the top of the first item is visible
boolean topOfFirstItemVisible = lvEvents.getChildAt(0).getTop() == 0;
// enabling or disabling the refresh layout
enable = firstItemVisible && topOfFirstItemVisible;
}
refreshLayout.setEnabled(enable);
}
});
refreshLayout.setOnRefreshListener(this);
setAppearance();
getSupportActionBar().setSubtitle(Courses.getSelectedCourseShortName());
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
/* (non-Javadoc)
* @see es.ugr.swad.swadroid.MenuExpandableListActivity#Override(android.os.Bundle)
*/
@Override
protected void onStart() {
super.onStart();
//Refresh ListView of events
refreshAdapter();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
switch (requestCode) {
case Constants.ROLLCALL_EVENTS_DOWNLOAD_REQUEST_CODE:
//Refresh ListView of events
refreshAdapter();
break;
}
}
private void refreshAdapter() {
mHandler.post(mRunnable);
}
private void refreshEvents() {
mProgressScreen.show();
Intent activity = new Intent(this, EventsDownload.class);
startActivityForResult(activity, Constants.ROLLCALL_EVENTS_DOWNLOAD_REQUEST_CODE);
}
/**
* It shows the SwipeRefreshLayout progress
*/
private void showSwipeProgress() {
refreshLayout.setRefreshing(true);
}
/**
* It shows the SwipeRefreshLayout progress
*/
private void hideSwipeProgress() {
refreshLayout.setRefreshing(false);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private void setAppearance() {
refreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
android.R.color.holo_red_light);
}
private boolean hasPendingEvents() {
boolean hasPendingEvents = false;
TextView sendingStateTextView;
int i = 0;
if ((lvEvents != null) && (lvEvents.getChildCount() > 0)) {
while(!hasPendingEvents && (i<lvEvents.getChildCount())) {
sendingStateTextView = (TextView) lvEvents.getChildAt(i).findViewById(R.id.sendingStateTextView);
hasPendingEvents = sendingStateTextView.getText().equals(getString(R.string.sendingStatePending));
i++;
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
switch (requestCode) {
case Constants.ROLLCALL_EVENTS_DOWNLOAD_REQUEST_CODE:
refreshAdapter();
break;
}
super.onActivityResult(requestCode, resultCode, intent);
}
return hasPendingEvents;
}
private void refreshAdapter() {
mHandler.post(mRunnable);
}
private void refreshEvents() {
mProgressScreen.show();
Intent activity = new Intent(this, EventsDownload.class);
startActivityForResult(activity, Constants.ROLLCALL_EVENTS_DOWNLOAD_REQUEST_CODE);
}
/**
* It shows the SwipeRefreshLayout progress
*/
private void showSwipeProgress() {
refreshLayout.setRefreshing(true);
}
/**
* It shows the SwipeRefreshLayout progress
*/
private void hideSwipeProgress() {
refreshLayout.setRefreshing(false);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private void setAppearance() {
refreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
android.R.color.holo_red_light);
}
private boolean hasPendingEvents() {
boolean hasPendingEvents = false;
TextView sendingStateTextView;
int i = 0;
if ((lvEvents != null) && (lvEvents.getChildCount() > 0)) {
while(!hasPendingEvents && (i<lvEvents.getChildCount())) {
sendingStateTextView = lvEvents.getChildAt(i).findViewById(R.id.sendingStateTextView);
hasPendingEvents = sendingStateTextView.getText().equals(getString(R.string.sendingStatePending));
i++;
}
}
return hasPendingEvents;
}
private void updateEvents() {
showSwipeProgress();
@ -249,48 +250,48 @@ public class Rollcall extends MenuExpandableListActivity implements
hideSwipeProgress();
}
/**
* It must be overriden by parent classes if manual swipe is enabled.
*/
@Override
public void onRefresh() {
if(!hasPendingEvents()) {
updateEvents();
} else {
AlertDialog cleanEventsDialog = DialogFactory.createWarningDialog(this,
-1,
R.string.areYouSure,
R.string.updatePendingEventsMsg,
R.string.yesMsg,
R.string.noMsg,
true,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
/**
* It must be overriden by parent classes if manual swipe is enabled.
*/
@Override
public void onRefresh() {
if(!hasPendingEvents()) {
updateEvents();
} else {
AlertDialog cleanEventsDialog = DialogFactory.createWarningDialog(this,
-1,
R.string.areYouSure,
R.string.updatePendingEventsMsg,
R.string.yesMsg,
R.string.noMsg,
true,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
updateEvents();
}
},
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
},
null);
updateEvents();
}
},
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
},
null);
cleanEventsDialog.show();
cleanEventsDialog.show();
}
hideSwipeProgress();
}
hideSwipeProgress();
}
private static class RefreshAdapterHandler extends Handler {
private static class RefreshAdapterHandler extends Handler {
private final WeakReference<Rollcall> mActivity;
private final WeakReference<Rollcall> mActivity;
public RefreshAdapterHandler(Rollcall activity) {
mActivity = new WeakReference<>(activity);
}
public RefreshAdapterHandler(Rollcall activity) {
mActivity = new WeakReference<>(activity);
}
}
}

View File

@ -74,7 +74,7 @@ public class UsersActivity extends MenuExpandableListActivity implements
/**
* ListView of users
*/
private static ListView lvUsers;
private ListView lvUsers;
/**
* Adapter for ListView of users
*/
@ -105,6 +105,16 @@ public class UsersActivity extends MenuExpandableListActivity implements
*/
private IntentIntegrator integrator;
/**
* Button for send attendances to SWAD
*/
private MenuItem mSendUsersMenuItem;
/**
* Button for clear all users for an event
*/
private MenuItem mCleanUsersMenuItem;
/* (non-Javadoc)
* @see es.ugr.swad.swadroid.MenuExpandableListActivity#onCreate(android.os.Bundle)
*/
@ -113,14 +123,14 @@ public class UsersActivity extends MenuExpandableListActivity implements
super.onCreate(savedInstanceState);
setContentView(R.layout.list_items_pulltorefresh);
refreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container_list);
emptyUsersTextView = (TextView) findViewById(R.id.list_item_title);
refreshLayout = findViewById(R.id.swipe_container_list);
emptyUsersTextView = findViewById(R.id.list_item_title);
View mProgressScreenView = findViewById(R.id.progress_screen);
mProgressScreen = new ProgressScreen(mProgressScreenView, refreshLayout,
getString(R.string.loadingMsg), this);
lvUsers = (ListView) findViewById(R.id.list_pulltorefresh);
lvUsers = findViewById(R.id.list_pulltorefresh);
lvUsers.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
@ -165,6 +175,9 @@ public class UsersActivity extends MenuExpandableListActivity implements
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
refreshAdapter();
setActionMenuItemsEnabled(true);
super.onActivityResult(requestCode, resultCode, intent);
}
private void refreshAdapter() {
@ -243,39 +256,50 @@ public class UsersActivity extends MenuExpandableListActivity implements
*/
@Override
public void onRefresh() {
setActionMenuItemsEnabled(false);
showSwipeProgress();
refreshUsers();
hideSwipeProgress();
setActionMenuItemsEnabled(true);
}
private String getUsersCodes() {
String usersCodes = "";
StringBuilder usersCodes = new StringBuilder();
List<UserAttendance> usersList = dbHelper.getUsersEvent(eventCode);
//Concatenate the user code of all users checked as present and separate them with commas
for(UserAttendance user : usersList) {
if(user.isUserPresent()) {
usersCodes += user.getId() + ",";
usersCodes.append(user.getId()).append(",");
}
}
//Remove final comma
if(!usersCodes.isEmpty()) {
usersCodes = usersCodes.substring(0, usersCodes.length()-1);
if(usersCodes.length() > 0) {
usersCodes = new StringBuilder(usersCodes.substring(0, usersCodes.length() - 1));
}
return usersCodes;
return usersCodes.toString();
}
private void scanQRCode() {
integrator.initiateScan();
}
private void setActionMenuItemsEnabled(boolean enabled) {
mSendUsersMenuItem.setEnabled(enabled);
mCleanUsersMenuItem.setEnabled(enabled);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.rollcall_activity_actions, menu);
mSendUsersMenuItem = menu.findItem(R.id.action_sendMsg);
mCleanUsersMenuItem = menu.findItem(R.id.action_cleanUsers);
return super.onCreateOptionsMenu(menu);
}
@ -302,6 +326,8 @@ public class UsersActivity extends MenuExpandableListActivity implements
return true;
case R.id.action_sendMsg:
setActionMenuItemsEnabled(false);
String usersCodes = getUsersCodes();
Intent activity = new Intent(getApplicationContext(),
@ -318,6 +344,8 @@ public class UsersActivity extends MenuExpandableListActivity implements
return true;
case R.id.action_cleanUsers:
setActionMenuItemsEnabled(false);
AlertDialog cleanDBDialog = DialogFactory.createWarningDialog(this,
-1,
R.string.areYouSure,
@ -358,7 +386,7 @@ public class UsersActivity extends MenuExpandableListActivity implements
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode) {
case Constants.PERMISSIONS_REQUEST_CAMERA: {

View File

@ -108,7 +108,7 @@ public class UsersCursorAdapter extends CursorAdapter {
String userFirstname = crypto.decrypt(cursor.getString(cursor.getColumnIndex("userFirstname")));
String userID = crypto.decrypt(cursor.getString(cursor.getColumnIndex("userID")));
final long userCode = cursor.getLong(cursor.getColumnIndex("userCode"));
String userPhoto = cursor.getString(cursor.getColumnIndex("photoPath"));
String userPhoto = crypto.decrypt(cursor.getString(cursor.getColumnIndex("photoPath")));
boolean present = Utils.parseIntBool(cursor.getInt(cursor.getColumnIndex("present")));
// Replace NULL value for strings returned by the webservice with the empty string
@ -127,10 +127,10 @@ public class UsersCursorAdapter extends CursorAdapter {
final ViewHolder holder = (ViewHolder) view.getTag();
view.setTag(holder);
holder.image = (ImageView) view.findViewById(R.id.imageView1);
holder.text1 = (TextView) view.findViewById(R.id.TextView1);
holder.text2 = (TextView) view.findViewById(R.id.TextView2);
holder.checkbox = (CheckBox) view.findViewById(R.id.check);
holder.image = view.findViewById(R.id.imageView1);
holder.text1 = view.findViewById(R.id.TextView1);
holder.text2 = view.findViewById(R.id.TextView2);
holder.checkbox = view.findViewById(R.id.check);
holder.checkbox.setChecked(present);
holder.checkbox.setOnClickListener(new View.OnClickListener() {
@ -152,12 +152,13 @@ public class UsersCursorAdapter extends CursorAdapter {
}
});
holder.image.setImageResource(R.drawable.usr_bl);
if(userPhoto != null) {
ImageFactory.displayImage(loader, crypto.decrypt(userPhoto), holder.image);
if((userPhoto != null) && !userPhoto.isEmpty()) {
ImageFactory.displayImage(loader, userPhoto, holder.image);
} else {
holder.image.setImageResource(R.drawable.usr_bl);
}
holder.text1.setText(userSurname1 + " " + userSurname2 + ", " + userFirstname);
holder.text1.setText(String.format("%s %s, %s", userSurname1, userSurname2, userFirstname));
holder.text2.setText(userID);
}
@ -166,10 +167,10 @@ public class UsersCursorAdapter extends CursorAdapter {
View view = inflater.inflate(R.layout.users_list_item, parent, false);
ViewHolder holder = new ViewHolder();
holder.image = (ImageView) view.findViewById(R.id.imageView1);
holder.text1 = (TextView) view.findViewById(R.id.TextView1);
holder.text2 = (TextView) view.findViewById(R.id.TextView2);
holder.checkbox = (CheckBox) view.findViewById(R.id.check);
holder.image = view.findViewById(R.id.imageView1);
holder.text1 = view.findViewById(R.id.TextView1);
holder.text2 = view.findViewById(R.id.TextView2);
holder.checkbox = view.findViewById(R.id.check);
view.setTag(holder);
return view;

View File

@ -122,7 +122,7 @@ public class UsersDownload extends Module {
if (userSurname1.equalsIgnoreCase(Constants.NULL_VALUE)) userSurname1 = "";
if (userSurname2.equalsIgnoreCase(Constants.NULL_VALUE)) userSurname2 = "";
if (userFirstname.equalsIgnoreCase(Constants.NULL_VALUE)) userFirstname = "";
if (userPhoto.equalsIgnoreCase(Constants.NULL_VALUE)) userPhoto = null;
if (userPhoto.equalsIgnoreCase(Constants.NULL_VALUE)) userPhoto = "";
//Inserts user data into database
dbHelper.insertUser(new User(userCode, null, userID, userNickname, userSurname1, userSurname2,
@ -153,7 +153,7 @@ public class UsersDownload extends Module {
if (numUsers == 0) {
Toast.makeText(this, R.string.noUsersAvailableMsg, Toast.LENGTH_LONG).show();
} else {
String msg = String.valueOf(numUsers) + " " + getResources().getString(R.string.usersUpdated);
String msg = numUsers + " " + getResources().getString(R.string.usersUpdated);
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}

View File

@ -14,6 +14,12 @@
</style>
</head>
<body bgcolor="white">
<h4>1.5.6 (futura)</h4>
<ul>
<li type="disc"><strong>Pasar lista:</strong> Corregida ejecución simultánea de varios envíos de la lista de asistencia</li>
</ul>
<body bgcolor="white">
<h4>1.5.5 (2019-07-08)</h4>
<ul>

View File

@ -14,6 +14,12 @@
</style>
</head>
<body bgcolor="white">
<h4>1.5.6 (upcoming)</h4>
<ul>
<li type="disc"><strong>Rollcall:</strong> Fixed SWADroid allows multiple simultaneous submissions of the attendance list</li>
</ul>
<body bgcolor="white">
<h4>1.5.5 (2019-07-08)</h4>
<ul>