package com.jobmineplus.mobile.activities.jbmnpls;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import android.animation.Animator;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.Intent;
import android.os.Bundle;
import android.util.SparseIntArray;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.CheckBox;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import com.jobmineplus.mobile.R;
import com.jobmineplus.mobile.activities.SimpleActivityBase;
import com.jobmineplus.mobile.database.pages.PageResult;
import com.jobmineplus.mobile.exceptions.JbmnplsException;
import com.jobmineplus.mobile.exceptions.JbmnplsLoggedOutException;
import com.jobmineplus.mobile.exceptions.JbmnplsLostStateException;
import com.jobmineplus.mobile.exceptions.JbmnplsParsingException;
import com.jobmineplus.mobile.widgets.HeightAnimation;
import com.jobmineplus.mobile.widgets.JbmnplsAdapterBase;
import com.jobmineplus.mobile.widgets.JbmnplsAdapterBase.Formatter;
import com.jobmineplus.mobile.widgets.JbmnplsAdapterBase.HIGHLIGHTING;
import com.jobmineplus.mobile.widgets.JbmnplsHttpClient;
import com.jobmineplus.mobile.widgets.JbmnplsLoadingAdapterBase;
import com.jobmineplus.mobile.widgets.JbmnplsRequestQueue;
import com.jobmineplus.mobile.widgets.Job;
import com.jobmineplus.mobile.widgets.Job.APPLY_STATUS;
import com.jobmineplus.mobile.widgets.Job.HEADER;
import com.jobmineplus.mobile.widgets.JobSearchDialog;
import com.jobmineplus.mobile.widgets.JobSearchDialog.OnJobSearchListener;
import com.jobmineplus.mobile.widgets.JobSearchProperties;
import com.jobmineplus.mobile.widgets.JobSearchProperties.JOBTYPE;
import com.jobmineplus.mobile.widgets.ListViewPlus;
import com.jobmineplus.mobile.widgets.ListViewPlus.OnVisualRowChangeListener;
import com.jobmineplus.mobile.widgets.table.SimpleHtmlParser;
import com.jobmineplus.mobile.widgets.table.TableParser;
import com.jobmineplus.mobile.widgets.table.TableParserOutline;
public class JobSearch extends JbmnplsPageListActivityBase implements
OnJobSearchListener, TableParser.OnTableParseListener,
OnScrollListener, OnClickListener, OnVisualRowChangeListener {
//======================
// Declaration Objects
//======================
public final static String PAGE_NAME = JobSearch.class.getName();
public final static int FETCH_MORE_REACH_BOTTOM_COUNT = 30;
private final static int VIEW_ITEM_POSITION_KEY = R.id.VIEW_ITEM_POSITION_KEY;
private final static int VIEW_ITEM_JOB_KEY = R.id.VIEW_ITEM_JOB_KEY;
private final static int REQUEST_CODE_DESCRIPTION = 1;
// Tab Names
public final static class PAGES {
final public static String NEW = "New";
final public static String READ = "Read";
final public static String SHORTLIST = "Shortlisted";
final public static String APPLIED = "Already Applied";
final public static String ALL = "All Jobs";
}
// Task Queue
private SearchRequestQueue taskQueue;
// Shortlist data structures
private final SparseIntArray jobPageArray = new SparseIntArray(200);
private Set<Integer> shortlistSet;
private List<Integer> readOrNewList;
private List<Job> readOrNewJobList;
// Search dialog and its properties
private JobSearchProperties properties;
private JobSearchDialog searchDialog;
// Job Search states
private boolean enableSearch = true;
private boolean firstSearch;
private boolean allJobsLoaded;
// Current list position
private int currentListPosition;
// Alert dialog
private AlertDialog.Builder alert;
// Sorting variables
private HEADER choseSortHeader;
private boolean choseSortAscend;
// Errors
private static enum Error {
INACTIVE, LOST_STATE, LOGGED_OUT, NONE
};
private Error stateError;
private RowAnimation rowAnimation;
// Shortlisting
private Job currentShortlistedJob;
private String shortlistedTab;
private boolean enableShortlisting;
private int numItemsFitInListView;
private boolean showGrowAnimation;
private View readView;
public final static HEADER[] SORT_HEADERS = {
HEADER.JOB_TITLE,
HEADER.EMPLOYER_NAME,
HEADER.LOCATION,
HEADER.OPENINGS,
HEADER.LAST_DAY_TO_APPLY,
HEADER.NUM_APPS
};
public static final TableParserOutline JOBSEARCH_OUTLINE =
new TableParserOutline("UW_CO_JOBRES_VW$scroll$0",
HEADER.JOB_IDENTIFIER,
HEADER.JOB_TITLE,
HEADER.EMPLOYER_NAME,
HEADER.UNIT_NAME,
HEADER.LOCATION,
HEADER.OPENINGS,
HEADER.APPLY,
HEADER.SHORTLIST,
HEADER.NUM_APPS,
HEADER.LAST_DAY_TO_APPLY);
protected final int[] WIDGET_RESOURCE_LIST = {
R.id.job_title, R.id.job_employer, R.id.location,
R.id.job_last_day, R.id.job_num_app_openings, R.id.star, R.id.loading };
//======================
// Overrided Methods
//======================
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
alert = new Builder(this);
alert.setNegativeButton(android.R.string.ok, null);
alert.create();
taskQueue = new SearchRequestQueue(this, null, null); // Initially, it will actually get set in parseWebpage()
firstSearch = true;
currentListPosition = 0;
allJobsLoaded = false;
enableShortlisting = true;
numItemsFitInListView = 1;
stateError = Error.NONE;
rowAnimation = new RowAnimation();
setOnRowChangeListener(this);
// Add the page tabs
createTab(PAGES.NEW);
createTab(PAGES.READ);
createTab(PAGES.SHORTLIST);
createTab(PAGES.APPLIED);
createTab(PAGES.ALL);
}
@Override
public String getPageName() {
return PAGE_NAME;
}
@Override
public String getUrl() {
return JbmnplsHttpClient.GET_LINKS.SEARCH;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (rowAnimation.isRunning()) {
return;
}
Job job = getCurrentList().get(position);
int jobId = job.getId();
if (!job.hasDescriptionData() && isReallyOnline()) {
taskQueue.addTask(SearchRequestQueue.DESCRIPTION);
}
// Deal with reading new jobs from new or all pages
String currentTab = getCurrentTabName();
if (currentTab == PAGES.NEW) {
readView = view;
} else if (currentTab == PAGES.ALL) {
// Search for the job in the new page, if exists, then transfer it from new to read
JbmnplsAdapterBase newAdapter = getAdapterByTab(PAGES.NEW);
int pos = newAdapter.getJobPosition(jobId);
if (pos != -1) {
JbmnplsAdapterBase readAdapter = getAdapterByTab(PAGES.READ);
readAdapter.add(job);
newAdapter.remove(pos);
}
}
goToDescription(jobId);
}
@Override
public HEADER[] getTableHeaders() {
return SORT_HEADERS;
}
@Override
public int[] getJobListItemResources() {
return WIDGET_RESOURCE_LIST;
}
@Override
protected int getActionBarId() {
return R.menu.actionbar_job_search;
}
@Override
protected void requestData() throws RuntimeException {
super.requestData();
firstSearch = true;
clearAllLists();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_search && canSearch()) {
showSearchDialog();
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
@Override
protected void parseWebpage(String html) {
properties = new JobSearchProperties();
/*
* Parse the original job search properties
*/
SimpleHtmlParser parser = new SimpleHtmlParser(html);
// State Number
parser.skipText("id='ICStateNum'");
String stateNum = parser.getAttributeInCurrentElement("value");
// ICSID
parser.skipText("id='ICSID'");
String icsID = parser.getAttributeInCurrentElement("value");
// Term
parser.skipText("win0divUW_CO_JOBSRCH_UW_CO_WT_SESSION", "input");
properties.term.set(parser.getAttributeInCurrentElement("value"));
// Job Level: Junior
parser.skipText("win0divUW_CO_JOBSRCH_UW_CO_COOP_JR", "onclick=");
properties.levelJunior.set(parser.getAttributeInCurrentElement("checked") != null);
// Job Level: Bachelors
parser.skipText("win0divUW_CO_JOBSRCH_UW_CO_BACHELOR", "onclick=");
properties.levelBachelors.set(parser.getAttributeInCurrentElement("checked") != null);
// Job Level: Intermediate
parser.skipText("win0divUW_CO_JOBSRCH_UW_CO_COOP_INT", "onclick=");
properties.levelIntermediate.set(parser.getAttributeInCurrentElement("checked") != null);
// Job Level: Masters
parser.skipText("win0divUW_CO_JOBSRCH_UW_CO_MASTERS", "onclick=");
properties.levelMasters.set(parser.getAttributeInCurrentElement("checked") != null);
// Job Level: Senior
parser.skipText("win0divUW_CO_JOBSRCH_UW_CO_COOP_SR", "onclick=");
properties.levelSenior.set(parser.getAttributeInCurrentElement("checked") != null);
// Job Level: Ph.D.
parser.skipText("win0divUW_CO_JOBSRCH_UW_CO_PHD", "onclick=");
properties.levelPhD.set(parser.getAttributeInCurrentElement("checked") != null);
// Employer name
parser.skipText("win0divUW_CO_JOBSRCH_UW_CO_EMPLYR_NAME", "input");
properties.employer.set(parser.getAttributeInCurrentElement("value"));
if (properties.employer.get() == null) {
throw new JbmnplsParsingException("Cannot find the value attribute for search employer");
}
// Job Title
parser.skipText("UW_CO_JOBSRCH_UW_CO_JOB_TITLE", "input");
properties.title.set(parser.getAttributeInCurrentElement("value"));
if (properties.title.get() == null) {
throw new JbmnplsParsingException("Cannot find the value attribute for search job title");
}
// Disciplines
parser.skipText("UW_CO_JOBSRCH_UW_CO_ADV_DISCP1", "selected=");
properties.disciplines1.set(parser.getTextInCurrentElement().replace("&", "&"));
parser.skipText("UW_CO_JOBSRCH_UW_CO_ADV_DISCP2", "selected=");
properties.disciplines2.set(parser.getTextInCurrentElement().replace("&", "&"));
parser.skipText("UW_CO_JOBSRCH_UW_CO_ADV_DISCP3", "selected=");
properties.disciplines3.set(parser.getTextInCurrentElement().replace("&", "&"));
// Location
parser.skipText("win0divUW_CO_JOBSRCH_UW_CO_LOCATION", "input");
properties.location.set(parser.getAttributeInCurrentElement("value"));
if (properties.location.get() == null) {
throw new JbmnplsParsingException("Cannot find the value attribute for search location");
}
// Job Search Filter
parser.skipText("win0divUW_CO_JOBSRCH_UW_CO_JS_JOBSTATUS", "selected=");
properties.filter.set(JobSearchProperties.FILTER.fromString(parser.getTextInCurrentElement()));
// Job Type
JOBTYPE jobType = JOBTYPE.getDefault();
try { // This try catch fixes errors when the user has never chosen a coop type, this is weird
parser.skipText("win0divUW_CO_JOBSRCH_UW_CO_JOB_TYPE", "checked=", "label");
jobType = JobSearchProperties.JOBTYPE.fromString(parser.getTextInCurrentElement());
} catch (JbmnplsParsingException e) {
}
properties.jobType.set(jobType);
taskQueue = new SearchRequestQueue(this, stateNum, icsID);
properties.acceptChanges();
}
@Override
public void onRowParse(TableParserOutline outline, Object... jobData) {
int id = (Integer)jobData[0];
if (!jobExists(id)) {
APPLY_STATUS appStatus = (APPLY_STATUS)jobData[6];
boolean hasBeenPlaced = false;
Job job = new Job( id, (String) jobData[1], (String)jobData[2],
(String) jobData[4], (Integer) jobData[5], appStatus,
(Date) jobData[9], (Integer) jobData[8]);
if (appStatus == APPLY_STATUS.ALREADY_APPLIED) {
hasBeenPlaced = true;
addJobToListByTabId(PAGES.APPLIED, job);
}
// Maintain the shortlisted ids database
if (((String)jobData[7]).equals("On Short List")) {
shortlistSet.add(id);
hasBeenPlaced = true;
addJobToListByTabId(PAGES.SHORTLIST, job);
} else {
shortlistSet.remove(id);
}
// If it has not been placed in any tab, then it is new
if (!hasBeenPlaced) {
readOrNewList.add(id);
readOrNewJobList.add(job);
}
// Place the jobs in different tabs
addJobToListByTabId(PAGES.ALL, job);
addJob(job);
}
}
@Override
protected void onRequestComplete(boolean pullData) {
// Coming from HomeActivity
if (firstSearch && pullData) {
showSearchDialog();
// If there was an error we will show an error
switch (stateError) {
case INACTIVE:
showAlert(getString(R.string.search_inactivity));
break;
case LOGGED_OUT:
showAlert(getString(R.string.search_logged_out));
break;
case LOST_STATE:
showAlert(getString(R.string.search_lost_state));
break;
default:
break;
}
stateError = Error.NONE;
} else {
super.onRequestComplete(pullData);
}
}
protected boolean jobExists(Job job) {
return jobExists(job.getId());
}
protected boolean jobExists(int id) {
return jobPageArray.indexOfKey(id) >= 0;
}
@Override
protected void addJob(Job job) {
jobPageArray.append(job.getId(), taskQueue.getCurrentPageNumber());
super.addJob(job);
}
@Override
public void clearAllLists() {
super.clearAllLists();
jobPageArray.clear();
}
@Override
protected void onResume() {
super.onResume();
enableShortlisting(isReallyOnline());
checkIfSearchExpired();
}
@Override
protected void onDestroy() {
taskQueue.cancelAllTasks();
super.onDestroy();
}
@Override
protected void scrollToTop() {
currentListPosition = 0;
super.scrollToTop();
}
@Override
protected void sort(HEADER header, boolean ascend) {
if (!allJobsLoaded && isReallyOnline()) {
choseSortHeader = header;
choseSortAscend = ascend;
taskQueue.addTask(SearchRequestQueue.SORT, getString(R.string.search_sort_getting_jobs));
} else {
super.sort(header, ascend);
}
}
@Override
protected void onNetworkConnectionChanged(boolean connected) {
super.onNetworkConnectionChanged(connected);
enableShortlisting(isReallyOnline());
showLoadingAtEnd(connected && isOnline() && !allJobsLoaded);
setSearchEnabled(connected);
if (connected) {
attachScrollListener(true);
} else {
taskQueue.cancelAllTasks();
attachScrollListener(false);
}
}
@Override
protected int getJobListItemLayout() {
return R.layout.job_search_widget;
}
@Override
protected long doOffine() {
// Get the shortlist ids from the databsae
PageResult r = pageDataSource.getPageData(client.getUsername(), Shortlist.PAGE_NAME);
if (r != null && r.ids != null) {
shortlistSet = new HashSet<Integer>(r.ids);
} else {
shortlistSet = new HashSet<Integer>();
}
doneLoadingAllJobs();
return super.doOffine();
}
//=================
// List Adapter
//=================
@Override
protected HIGHLIGHTING formatJobListItem(int position, Job job,
View[] elements, View layout) {
layout.setTag(VIEW_ITEM_POSITION_KEY, position);
layout.setTag(VIEW_ITEM_JOB_KEY, job);
Formatter.setText((TextView)elements[0], job.getTitle());
Formatter.setText((TextView)elements[1], job.getEmployer(), true);
Formatter.setText((TextView)elements[2], job.getLocation());
Formatter.setDate((TextView)elements[3], job.getLastDateToApply(), "Closes by");
Formatter.setText((TextView)elements[4], "Openings: " + job.getNumberOfOpenings() + " | Apps #: " + job.getNumberOfApplications());
// Checkbox
CheckBox box = (CheckBox)elements[5];
box.setOnClickListener(this);
box.setChecked(isShortlisted(job));
box.setEnabled(!box.isChecked() && enableShortlisting);
View progress = elements[6];
if (currentShortlistedJob != null && job.getId() == currentShortlistedJob.getId()) {
box.setVisibility(View.GONE);
progress.setVisibility(View.VISIBLE);
} else {
box.setVisibility(View.VISIBLE);
progress.setVisibility(View.GONE);
}
return HIGHLIGHTING.NORMAL;
}
//=======================
// Handle Search Icon
//=======================
@Override
protected void onlineModeChanged(boolean flag) {
setSearchEnabled(flag);
enableShortlisting(isReallyOnline());
// Coming in offline and going online, we need to get the new data
if (firstSearch && flag) {
if (isReallyOnline()) {
requestData();
}
} else {
if (flag) {
// Goes online and needs to continue to get more jobs
if (!allJobsLoaded) {
if (!taskQueue.hasLoaded100Jobs()) {
taskQueue.addTask(SearchRequestQueue.VIEW100);
}
showLoadingAtEnd(true);
attachScrollListener(true);
}
} else {
// Go offline
taskQueue.cancelAllTasks();
showLoadingAtEnd(false);
attachScrollListener(false);
}
}
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem switchButton = menu.findItem(R.id.action_search);
setSearchIcon(switchButton);
return super.onPrepareOptionsMenu(menu);
}
private boolean canSearch() {
return enableSearch && isReallyOnline();
}
private void setSearchEnabled(boolean enable) {
enableSearch = enable;
supportInvalidateOptionsMenu();
}
private void setSearchIcon(MenuItem button) {
boolean ableToSearch = canSearch();
button.setEnabled(ableToSearch);
if (ableToSearch) {
button.setIcon(R.drawable.ic_action_search);
} else {
button.setIcon(R.drawable.ic_action_search_disabled);
}
}
@Override
public void onPageSelected(int index) {
super.onPageSelected(index);
if (!allJobsLoaded) {
showLoadingAtEnd(true);
attachScrollListener(true);
}
}
//=======================
// Activity for result
//=======================
@Override
protected void goToDescription(int jobId) {
Intent in = new Intent(this, Description.class);
in.putExtra(EXTRA_JOB_ID, Integer.toString(jobId));
startActivityForResult(in, REQUEST_CODE_DESCRIPTION);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_DESCRIPTION) {
if (resultCode == Activity.RESULT_OK) {
// Successfully read the job description
String tab = getCurrentTabName();
if (readView != null && tab == PAGES.NEW) {
Job job = (Job)readView.getTag(VIEW_ITEM_JOB_KEY);
addJobToListByTabId(PAGES.READ, job);
getAdapterByTab(PAGES.READ).notifyDataSetChanged();
// Animate the row removal
if (!rowAnimation.isRunning()) {
rowAnimation.setDelay(400); // Glitch where the animation wont animate unless it waits a little
rowAnimation.startShink(readView);
rowAnimation.setDelay(0);
} else {
// If somehow fails and not looking at new page, just remove it
int pos = (Integer)readView.getTag(VIEW_ITEM_POSITION_KEY);
JbmnplsAdapterBase adapter = getAdapterByTab(PAGES.NEW);
adapter.remove(pos);
jobsToDatabase();
}
}
} else {
// Cancelled reading the job description
readView = null;
}
}
super.onActivityResult(requestCode, resultCode, data);
}
//=======================
// OnListViewListener
//=======================
@Override
public void onVisuallyAddedRows(ListView listView) {
String tab = getCurrentTabName();
if (showGrowAnimation && tab == PAGES.SHORTLIST) {
// Detect if user can see it
if (listView.getChildCount() <= numItemsFitInListView) {
// Ignore the requestLayout() improperly warning
rowAnimation.startGrow(getListViewByTab(tab).getLastChild());
}
showGrowAnimation = false;
}
}
@Override
public void onVisuallyRemovedRows(ListView listView) {
}
//=======================
// OnJobSearchListener
//=======================
@Override
public void onJobTypeChange(Spinner spinner, JobSearchProperties.JOBTYPE type) {
properties.jobType.set(type);
taskQueue.addTask(SearchRequestQueue.JOBTYPE);
}
@Override
public void onSearch(JobSearchProperties prop) {
if (isReallyOnline()) {
taskQueue.cancelAllTasks();
taskQueue.addTask(SearchRequestQueue.SEARCH, getString(R.string.search_searching_dialog));
} else {
hideSearchDialog();
showAlert(getString(R.string.search_searching_offline));
}
}
@Override
public void onCancel() {
if (taskQueue.isRunning()) {
// Cancel only if job type changes when pressing cancel on the search dialog
if (taskQueue.getCurrentCommand() == SearchRequestQueue.JOBTYPE) {
taskQueue.cancelCurrent(true);
}
}
if (firstSearch && getListByTab(PAGES.ALL).isEmpty()) {
finish();
}
}
//===========================
// OnCheckedChange Listener
//===========================
@Override
public void onClick(View v) {
if (v instanceof CheckBox) {
CheckBox box = (CheckBox)v;
// Get job from a parent layout's tag
ViewGroup vg = (ViewGroup)box.getParent();
View view = (View)vg.getParent();
Job job = (Job)view.getTag(VIEW_ITEM_JOB_KEY);
currentShortlistedJob = job;
shortlistedTab = getCurrentTabName();
taskQueue.addTask(SearchRequestQueue.SHORTLIST);
enableShortlisting(false);
// Show loading symbol
View bar = vg.findViewById(R.id.loading);
bar.setVisibility(View.VISIBLE);
box.setVisibility(View.GONE);
}
}
//====================
// Scroll Listener
//====================
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
showGrowAnimation = false;
numItemsFitInListView = Math.max(numItemsFitInListView, visibleItemCount);
// Have the scroll update only on new or all lists
String tab = getCurrentTabName();
if (tab == PAGES.ALL || tab == PAGES.NEW) {
currentListPosition = firstVisibleItem + visibleItemCount;
fetchMoreIfNeeded();
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
//==================================
// Row addition/removal animation
//==================================
class RowAnimation extends HeightAnimation {
private boolean isGrowing;
private String removalTab;
public void startGrow(View v) {
start(v, true);
}
public void startShink(View v) {
start(v, false);
removalTab = getCurrentTabName();
}
@Override
public void start(View v, boolean grow) {
isGrowing = grow;
super.start(v, grow);
}
private void removeJob() {
final int position = (Integer)getView().getTag(VIEW_ITEM_POSITION_KEY);
JbmnplsAdapterBase adapter = getAdapterByTab(removalTab); // TODO make this work with read to remove
ArrayList<Job> jobs = adapter.getList();
if (jobs.size() > 0) {
if (jobs.remove(position) == null) {
throw new NullPointerException("Unable to find the job removed from animation");
}
adapter.notifyDataSetChanged();
}
removalTab = null;
jobsToDatabase();
}
@Override
public void onAnimationCancel(Animator animator) {
if (!isGrowing) {
removeJob();
}
// super.onAnimationCancel(animator);
}
@Override
public void onAnimationEnd(Animator animator) {
if (!isGrowing) {
removeJob();
}
// super.onAnimationEnd(animator);
}
}
//=======================
// Methods for all Tabs
//=======================
protected void showLoadingAtEnd(boolean flag) {
for (int i = 0; i < numOfTabs(); i++) {
JbmnplsLoadingAdapterBase adapter = (JbmnplsLoadingAdapterBase)getAdapterByIndex(i);
if (adapter != null) {
if (flag) {
if (adapter.getCount() > numItemsFitInListView) {
adapter.showLoadingAtEnd(true);
} else {
adapter.showLoadingAtEnd(false);
}
} else {
adapter.showLoadingAtEnd(false);
}
}
}
}
protected void attachScrollListener(boolean flag) {
OnScrollListener listener = flag ? this : null;
for (int i = 0; i < numOfTabs(); i++) {
ListView list = getListViewByIndex(i);
if (list != null) {
list.setOnScrollListener(listener);
}
}
}
//==================
// Miscellaneous
//==================
private boolean checkIfSearchExpired() {
// When 20 min has past when nothing has posted
boolean flag = searchDialog != null && !searchDialog.isShowing() && !client.isLoggedIn();
if (flag) {
// Reset to first time search and show the dialog again
stateError = Error.INACTIVE;
requestData();
}
return flag;
}
private void showSearchDialog() {
// Need to get search values, so we will get them
if (properties == null) {
requestData();
return;
}
if (canSearch()) {
if (searchDialog == null) {
searchDialog = new JobSearchDialog(this);
searchDialog.setOnJobSearchListener(this);
searchDialog.setProperties(properties);
}
searchDialog.show();
}
}
protected void hideSearchDialog() {
if (searchDialog != null) {
searchDialog.dismiss();
}
}
protected void showAlert(String message) {
alert.setMessage(message);
alert.show();
}
// Only fetch more on all or new pages
protected void fetchMoreIfNeeded() {
String tab = getCurrentTabName();
if (tab == PAGES.ALL || tab == PAGES.NEW) {
int currentlyLoaded = Math.min(getListByTab(PAGES.ALL).size(), getListByTab(PAGES.NEW).size());
if (currentlyLoaded - currentListPosition < FETCH_MORE_REACH_BOTTOM_COUNT // If near the bottom of the list
&& taskQueue.hasLoaded100Jobs() // Has loaded 100 items
&& !allJobsLoaded
&& !taskQueue.isRunning()) { // Not running any task currently
taskQueue.addTask(SearchRequestQueue.NEXTPAGE);
}
}
}
protected void doneLoadingAllJobs() {
allJobsLoaded = true;
showLoadingAtEnd(false);
attachScrollListener(false);
}
protected boolean isShortlisted(Job job) {
return job != null && isShortlisted(job.getId());
}
protected boolean isShortlisted(int id) {
return shortlistSet.contains(id);
}
protected void enableShortlisting(boolean flag) {
if (enableShortlisting != flag) {
for (int l = 0; l < numOfTabs(); l++) {
ListView list = getListViewByIndex(l);
if (list != null) {
for (int i = 0; i < list.getChildCount(); i++) {
View view = list.getChildAt(i);
CheckBox box = (CheckBox)view.findViewById(R.id.star);
// If the row is shortlisted, then check it and enable it
if (isShortlisted((Job)view.getTag(VIEW_ITEM_JOB_KEY)) || box.isChecked()) {
box.setEnabled(false);
box.setChecked(true);
} else {
box.setEnabled(flag);
}
}
}
}
enableShortlisting = flag;
}
}
protected void removeAllItemLoadingImage() {
for (int l = 0; l < numOfTabs(); l++) {
ListView list = getListViewByIndex(l);
if (list != null) {
for (int i = 0; i < list.getChildCount(); i++) {
View view = list.getChildAt(i);
view.findViewById(R.id.star).setVisibility(View.VISIBLE);
view.findViewById(R.id.loading).setVisibility(View.GONE);
}
}
}
}
//===========================
// All Purpose Task Runner
//===========================
private final class SearchRequestQueue extends JbmnplsRequestQueue<Void> {
public final static int INITIAL_RESULT_COUNT = 25;
public final static int RESULT_COUNT_100 = 100;
// Task States
public static final int SEARCH = 0;
public static final int VIEW100 = 1;
public static final int NEXTPAGE = 2;
public static final int SHORTLIST = 3;
public static final int JOBTYPE = 4;
public static final int SORT = 5;
public static final int DESCRIPTION = 6;
// Task Response states
protected static final int JOBTYPE_UNAUTH = 7;
// Shortlist check string matches
public static final String SHORTLIST_SUCCESS_STRING = "The job has been added successfully";
// Request States
private String lastSearchedHtml = null;
private int currentPage;
private int numJobs;
private int totalPages;
private boolean hasLoaded100;
private final JobSearch activity;
TableParser tableParser;
public SearchRequestQueue(JobSearch a, String stateNumber, String id) {
super(a, SimpleActivityBase.client, getUrl(), stateNumber, id);
activity = a;
currentPage = 0;
hasLoaded100 = false;
}
public int getCurrentPageNumber() {
return currentPage;
}
public boolean hasLoaded100Jobs() {
return hasLoaded100;
}
private void parseHtmlForJobs() {
readOrNewList = new ArrayList<Integer>();
readOrNewJobList = new ArrayList<Job>();
tableParser.execute(JOBSEARCH_OUTLINE, lastSearchedHtml);
if (!readOrNewList.isEmpty()) {
// Now we can get the jobs from their ids from the database
ArrayList<Job> jobs = jobDataSource.getJobsByIdList(readOrNewList);
if (jobs != null && !jobs.isEmpty()) {
for (int i = 0; i < jobs.size(); i++) {
// Add the job to read if it has description data
Job job = jobs.get(i);
if (job.hasDescriptionData()) {
addJobToListByTabId(PAGES.READ, job);
} else {
addJobToListByTabId(PAGES.NEW, job);
}
}
} else if (!readOrNewJobList.isEmpty()) {
// Add all the jobs if this is the first time searching
getListByTab(PAGES.NEW).addAll(readOrNewJobList);
}
}
readOrNewJobList = null;
readOrNewList = null;
}
@Override
protected Integer doInBackground(Integer... params) throws IOException {
tableParser = new TableParser();
tableParser.setOnTableRowParse(activity);
ArrayList<Integer> shortlistIds = null;
SimpleHtmlParser parser = null;
String response = null;
switch(getCurrentCommand()) {
case SEARCH:
showGrowAnimation = false;
List<NameValuePair> data = new ArrayList<NameValuePair>();
properties.addChangesToPostData(data);
lastSearchedHtml = doPost("UW_CO_JOBSRCHDW_UW_CO_DW_SRCHBTN", data);
// Get the shortlist ids from the databsae
PageResult r = pageDataSource.getPageData(client.getUsername(), Shortlist.PAGE_NAME);
if (r != null && r.ids != null) {
shortlistSet = new HashSet<Integer>(r.ids);
} else {
shortlistSet = new HashSet<Integer>();
}
currentPage = 1;
// Parse the jobs out
clearAllLists();
parseHtmlForJobs();
properties.acceptChanges();
updatePageJobInfo();
// Write the shortlisted ids to the database
shortlistIds = new ArrayList<Integer>(shortlistSet);
pageDataSource.addPageIds(client.getUsername(), Shortlist.PAGE_NAME, shortlistIds);
// Make a new task to view 100 jobs if there are more than 25 jobs
if (numJobs > INITIAL_RESULT_COUNT && !hasLoaded100) {
addTask(VIEW100);
}
break;
case VIEW100:
view100();
fetchMoreIfNeeded();
break;
case NEXTPAGE:
getNextPage();
parseHtmlForJobs();
// If finished loading all the pages, then remove the scroll event
if (currentPage != totalPages) {
fetchMoreIfNeeded();
}
break;
case SHORTLIST:
int goToPage = jobPageArray.get(currentShortlistedJob.getId());
String name = null;
if (currentPage != goToPage) {
// Go to the page
while(currentPage != goToPage) {
if (currentPage > goToPage) {
getPrevPage();
} else if (currentPage < goToPage) {
getNextPage();
}
}
}
// Get the shortlist job index from html
parser = new SimpleHtmlParser(lastSearchedHtml);
parser.skipText(currentShortlistedJob.getId() + "", "id='UW_CO_SLIST_HL$");
name = parser.getAttributeInCurrentElement("id");
response = doPost(name);
if (!response.contains(SHORTLIST_SUCCESS_STRING)) {
throw new JbmnplsLostStateException("Was not able to shortlist");
}
// Write the shortlisted ids to the database
shortlistSet.add(currentShortlistedJob.getId());
shortlistIds = new ArrayList<Integer>(shortlistSet);
pageDataSource.addPageIds(client.getUsername(), Shortlist.PAGE_NAME, shortlistIds);
break;
case JOBTYPE:
response = doPost("TYPE_COOP", new BasicNameValuePair("TYPE_COOP",
properties.jobType.get().getIndex() + ""));
if (response.contains(getString(R.string.job_search_type_unauth))) {
properties.jobType.rejectChange();
return JOBTYPE_UNAUTH;
} else {
properties.jobType.updateChange();
}
break;
case SORT:
if (!hasLoaded100) {
view100();
}
while (currentPage < totalPages) {
getNextPage();
}
break;
case DESCRIPTION:
doPost("UW_CO_JOBTITLE_HL$0");
break;
default:
return UNKNOWN_COMMAND;
}
return NO_PROBLEM;
}
@Override
protected boolean checkForInActivity() {
return checkIfSearchExpired();
}
private void updatePageJobInfo() {
// Find the number of jobs in the result
SimpleHtmlParser parser = new SimpleHtmlParser(lastSearchedHtml);
parser.skipText("PSGRIDCOUNTER");
String pageInfo = parser.getTextInCurrentElement();
try {
int ofIndex = pageInfo.lastIndexOf("of");
if (getListByTab(PAGES.ALL).size() <= 1) {
numJobs = 0;
currentPage = 1;
totalPages = 1;
} else {
numJobs = Integer.parseInt(pageInfo.substring(ofIndex + 2).replaceAll(" ", ""));
int dashIndex = pageInfo.indexOf("-");
int jobsPerPage = hasLoaded100 ? RESULT_COUNT_100 : INITIAL_RESULT_COUNT;
currentPage = (int)Math.ceil(Integer.parseInt(pageInfo.substring(0, dashIndex)) * 1.0 / jobsPerPage);
totalPages = (int)Math.ceil(numJobs * 1.0 / jobsPerPage);
}
} catch (NumberFormatException e) {
throw new JbmnplsParsingException("Cannot find the number of jobs in the search result");
}
}
private int view100() throws JbmnplsLoggedOutException, IOException {
if (!hasLoaded100) {
lastSearchedHtml = doPost("UW_CO_JOBRES_VW$hviewall$0");
parseHtmlForJobs();
// Update the page total
hasLoaded100 = true;
updatePageJobInfo();
}
return NO_PROBLEM;
}
private int getNextPage() throws JbmnplsLoggedOutException, IOException {
if (currentPage == totalPages) {
throw new JbmnplsLostStateException("Cannot go to next page because there is no more pages.");
}
lastSearchedHtml = doPost("UW_CO_JOBRES_VW$hdown$0");
updatePageJobInfo();
return NO_PROBLEM;
}
private int getPrevPage() throws JbmnplsLoggedOutException, IOException {
if (currentPage == 0) {
throw new JbmnplsLostStateException("Cannot go to previous page because current page is at 0.");
}
lastSearchedHtml = doPost("UW_CO_JOBRES_VW$hup$0");
updatePageJobInfo();
return NO_PROBLEM;
}
@Override
protected void onCancelled() {
// Reset shortlist state when cancelled
enableShortlisting(true);
currentShortlistedJob = null;
shortlistedTab = null;
removeAllItemLoadingImage();
cancelAllTasks();
super.onCancelled();
}
private void cancelled() {
switch(getCurrentCommand()) {
case SORT:
resetSortingMenu();
break;
case SHORTLIST:
// Revert check of the last item checked, check all pages for the shortlist id
for (int l = 0; l < numOfTabs(); l++) {
ListView list = getListViewByIndex(l);
if (list != null) {
for (int i = 0; i < list.getChildCount(); i++) {
View view = list.getChildAt(i);
CheckBox box = (CheckBox) view.findViewById(R.id.star);
Job job = (Job) view.getTag(VIEW_ITEM_JOB_KEY);
if (currentShortlistedJob.getId() == job.getId()) {
box.setChecked(false);
break;
}
}
}
}
enableShortlisting(true);
currentShortlistedJob = null;
shortlistedTab = null;
removeAllItemLoadingImage();
break;
}
taskQueue.clear();
}
@Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
if (result == CANCELLED) {
cancelled();
return;
}
// Finish off some of the commands
switch(getCurrentCommand()) {
case SEARCH:
// Just finished search, reset all flags
firstSearch = false; // No that you search, it is not the first time anymore
resetSortingMenu();
if (currentPage == totalPages) {
doneLoadingAllJobs();
} else {
attachScrollListener(true);
allJobsLoaded = false;
showLoadingAtEnd(true);
}
getSupportActionBar().setSubtitle(null); // Remove subtitle after coming from offline
hideSearchDialog();
onRequestComplete(true);
scrollToTop();
break;
case NEXTPAGE:
case VIEW100:
if (currentPage == totalPages) {
doneLoadingAllJobs();
}
onRequestComplete(true);
break;
case SORT:
doneLoadingAllJobs();
onRequestComplete(true);
activity.sort(choseSortHeader, choseSortAscend);
break;
case SHORTLIST:
enableShortlisting(true);
removeAllItemLoadingImage();
// Depending on what user is seeing and where it came from, this handles post shortlisting
String currentTab = getCurrentTabName();
int id = currentShortlistedJob.getId();
boolean searchAndRemoveFromReadOrNewAdapter = false;
addJobToListByTabId(PAGES.SHORTLIST, currentShortlistedJob);
if (currentTab == PAGES.SHORTLIST) {
// If on the shortlisted page, then show the animation soon of it appearing
showGrowAnimation = true;
if (shortlistedTab != PAGES.APPLIED) {
searchAndRemoveFromReadOrNewAdapter = true;
}
} else if (currentTab == PAGES.NEW || currentTab == PAGES.READ) {
// Currently looking at new or read, we will show the animation if it exists if
// not shortlisted from applied (or shortlist) page
if (shortlistedTab != PAGES.APPLIED) {
ListViewPlus listView = getListViewByTab(currentTab);
boolean found = false;
// Check to see if the user can see the shortlisted job, if so, then animate it
for (int i = 0; i < listView.getCount(); i++) {
View v = listView.getChildAt(i);
if (v == null) {
break;
}
if (((Job)v.getTag(VIEW_ITEM_JOB_KEY)).getId() == id) {
found = true;
// If can animate, otherwise just remove it (this will be rare)
if (!rowAnimation.isRunning()) {
rowAnimation.startShink(v);
shortlistedTab = currentTab;
} else {
listView.removeViewAt((Integer)v.getTag(VIEW_ITEM_POSITION_KEY));
jobsToDatabase();
}
break;
}
}
// If user cannot see the shortlisted row, then we will just remove it
if (!found) {
searchAndRemoveFromReadOrNewAdapter = true;
}
}
} else {
// Currently viewing all or applied
if (shortlistedTab != PAGES.APPLIED) {
// If we shortlisted from all, then we have to remove from read or new page
searchAndRemoveFromReadOrNewAdapter = true;
}
}
// Search for the job and remove from either read or new
if (searchAndRemoveFromReadOrNewAdapter) {
// See if new page has the job
JbmnplsAdapterBase newAdapter = getAdapterByTab(PAGES.NEW);
if (!newAdapter.removeByJobId(id)) {
JbmnplsAdapterBase readAdapter = getAdapterByTab(PAGES.READ);
if (!readAdapter.removeByJobId(id)) {
throw new JbmnplsException("Cannot find shortlisted job in new or read when it was suppose to be here!");
}
}
jobsToDatabase();
}
updateLists();
currentShortlistedJob = null;
shortlistedTab = null;
break;
}
// Parse the results
switch(result) {
case JOBTYPE_UNAUTH:
searchDialog.setJobTypeSpinner(properties.jobType.get());
// If there is no other tasks (such as searching) do not show the alert
if (taskQueue.isEmpty()) {
showAlert(getString(R.string.job_search_type_unauth));
}
break;
case UNKNOWN_COMMAND:
case PARSING_ERROR:
goToHomeActivity(getString(R.string.search_parsing_error_message));
doneLoadingAllJobs();
return;
case LOST_STATE_RESULT:
doneLoadingAllJobs();
stateError = Error.LOST_STATE;
requestData();
break;
case LOGOUT_RESULT:
// Go back to home screen? and show a fail?
doneLoadingAllJobs();
stateError = Error.LOGGED_OUT;
requestData();
break;
}
}
}
}