Commit 6703ac22 authored by Roger Barton's avatar Roger Barton
Browse files

Added job offers page, need refactoring and more testing

Created job offers list, getting data from api, job detail page
parent 1780c988
Pipeline #4604 failed with stages
......@@ -50,7 +50,15 @@
android:value="ch.amiv.android_app.core.MainActivity" />
</activity>
<activity
android:name=".core.EventDetailActivity"
android:name=".events.EventDetailActivity"
android:configChanges="orientation|layoutDirection|locale"
android:theme="@style/AppThemeLight">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="ch.amiv.android_app.core.MainActivity" />
</activity>
<activity
android:name=".jobs.JobDetailActivity"
android:configChanges="orientation|layoutDirection|locale"
android:theme="@style/AppThemeLight">
<meta-data
......
......@@ -65,7 +65,7 @@ public class MainActivity extends AppCompatActivity {
}
});
View logo = findViewById(R.id.LogoImage);
View logo = findViewById(R.id.logoImage);
if(logo != null) {
Animation animation = AnimationUtils.loadAnimation(this, R.anim.item_anim_pop);
animation.setDuration(150);
......
package ch.amiv.android_app.core;
import android.support.v7.widget.RecyclerView;
public abstract class BaseRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public void RefreshData(){
BuildDataset();
notifyDataSetChanged();
}
public void BuildDataset(){}
}
package ch.amiv.android_app.core;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
......@@ -15,18 +15,28 @@ import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import ch.amiv.android_app.R;
import ch.amiv.android_app.events.EventsListAdapter;
import ch.amiv.android_app.jobs.JobListAdapter;
/**
* An example fragment, the central view in MainActivity, for showing a list, should be replaced by a standard fragment with a custom recyclerView, create one different class for different views
*/
public class ListFragment extends Fragment {
int pagePosition; //the fragments page in the pageview of the main activity
RecyclerView recyclerView;
EventsListAdapter recylcerAdaper;
RecyclerView.LayoutManager recyclerLayoutAdapter;
private int pagePosition; //the fragments page in the pageview of the main activity
public static final class PageType {
public static final int COUNT = 3;
public static final int EVENTS = 0;
public static final int NOTIFICATIONS = 1;
public static final int JOBS = 2;
}
SwipeRefreshLayout swipeRefreshLayout;
Requests.OnDataReceivedCallback cancelRefreshCallback = new Requests.OnDataReceivedCallback() {
private RecyclerView recyclerView;
private BaseRecyclerAdapter recyclerAdapter;
private RecyclerView.LayoutManager recyclerLayoutAdapter;
private SwipeRefreshLayout swipeRefreshLayout;
private Requests.OnDataReceivedCallback cancelRefreshCallback = new Requests.OnDataReceivedCallback() {
@Override
public void OnDataReceived() {
swipeRefreshLayout.setRefreshing(false);
......@@ -52,31 +62,53 @@ public class ListFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pagePosition = getArguments() != null ? getArguments().getInt("pagePosition") : 1;
pagePosition = getArguments() != null ? getArguments().getInt("pagePosition") : 0;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
swipeRefreshLayout = getView().findViewById(R.id.swipeRefresh);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if(pagePosition == 0 && getActivity() instanceof MainActivity)
if(!(getActivity() instanceof MainActivity))
return;
if(pagePosition == PageType.EVENTS)
Requests.FetchEventList(getContext(), ((MainActivity)getActivity()).onEventsListUpdatedCallback, cancelRefreshCallback, "");
else if (pagePosition == PageType.JOBS)
Requests.FetchJobList(getContext(), ((MainActivity)getActivity()).onJobsListUpdatedCallback, cancelRefreshCallback, "");
}
});
//refresh on activity start
swipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
if(pagePosition == 0 && getActivity() instanceof MainActivity) {
if(!(getActivity() instanceof MainActivity))
return;
if(pagePosition == PageType.EVENTS) {
swipeRefreshLayout.setRefreshing(true);
Requests.FetchEventList(getContext(), ((MainActivity)getActivity()).onEventsListUpdatedCallback, cancelRefreshCallback, "");
}
else if(pagePosition == PageType.JOBS){
swipeRefreshLayout.setRefreshing(true);
Requests.FetchJobList(getContext(), ((MainActivity)getActivity()).onJobsListUpdatedCallback, cancelRefreshCallback, "");
}
}
});
//Disable the refresh animation after a timeout
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
swipeRefreshLayout.setRefreshing(false);
}
}, 1000*15);
recyclerView = getView().findViewById(R.id.recyclerView);
// use this setting to improve performance if you know that changes
......@@ -88,36 +120,39 @@ public class ListFragment extends Fragment {
recyclerView.setLayoutManager(recyclerLayoutAdapter);
// specify an adapter (see also next example)
if(pagePosition == 0) {
recylcerAdaper = new EventsListAdapter(getActivity());
recyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(getContext(), R.anim.layout_anim_falldown));
recyclerView.setAdapter(recylcerAdaper);
}
if(pagePosition == PageType.EVENTS)
recyclerAdapter = new EventsListAdapter(getActivity());
else if (pagePosition == PageType.JOBS)
recyclerAdapter = new JobListAdapter(getActivity());
recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
if(recyclerAdapter != null) {
recyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(getContext(), R.anim.layout_anim_falldown));
recyclerView.setAdapter(recyclerAdapter);
AnimateList(null);
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
//Used to show feedback when touching item
recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
return false;
}
}
});
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) { }
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { }
});
AnimateList(null);
}
}
public void RefreshList(boolean animate)
{
recylcerAdaper.RefreshData();
if(recyclerAdapter == null)
return;
swipeRefreshLayout.setRefreshing(false);
recyclerAdapter.RefreshData();
if(animate)
AnimateList(null);
}
......@@ -137,8 +172,8 @@ public class ListFragment extends Fragment {
public void onResume() {
super.onResume();
if(recylcerAdaper != null)
recylcerAdaper.RefreshData();
if(recyclerAdapter != null)
recyclerAdapter.RefreshData();
}
/**
......@@ -147,6 +182,9 @@ public class ListFragment extends Fragment {
*/
public void AnimateList(View view)
{
if(recyclerAdapter == null)
return;
getActivity().runOnUiThread(new Runnable() {
public void run() {
recyclerView.invalidate();
......
package ch.amiv.android_app.core;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.TextView;
import ch.amiv.android_app.R;
public final class ListHelper {
public static class Pair {
public int type;
public Object value;
public Pair(int type, Object value) {
this.type = type;
this.value = value;
}
}
public static class HeaderHolder extends RecyclerView.ViewHolder {
public TextView nameField;
public HeaderHolder(View view) {
super(view);
nameField = view.findViewById(R.id.titleField);
}
}
public static class SpaceHolder extends RecyclerView.ViewHolder {
public View space;
public SpaceHolder(View view) {
super(view);
space = view.findViewById(R.id.space);
}
}
}
......@@ -14,6 +14,7 @@ import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
......@@ -22,6 +23,9 @@ import android.widget.TextView;
import ch.amiv.android_app.R;
import ch.amiv.android_app.checkin.BarcodeIdActivity;
import ch.amiv.android_app.events.EventDetailActivity;
import ch.amiv.android_app.events.Events;
import ch.amiv.android_app.jobs.JobDetailActivity;
/**
* This is the first screen. features: drawer, pageview with bottom navigation bar and within each page a list view.
......@@ -42,14 +46,21 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
@Override
public void OnDataReceived() {
Requests.FetchEventSignups(getApplicationContext(), onSignupsUpdatedCallback, null, "");
pagerAdapter.RefreshCurrentList(true);
pagerAdapter.RefreshPage(ListFragment.PageType.EVENTS, true);
}
};
public Requests.OnDataReceivedCallback onJobsListUpdatedCallback = new Requests.OnDataReceivedCallback() {
@Override
public void OnDataReceived() {
pagerAdapter.RefreshPage(ListFragment.PageType.JOBS, true);
}
};
private Requests.OnDataReceivedCallback onSignupsUpdatedCallback = new Requests.OnDataReceivedCallback() {
@Override
public void OnDataReceived() {
pagerAdapter.RefreshCurrentList(false);
pagerAdapter.RefreshPage(ListFragment.PageType.EVENTS, false);
}
};
......@@ -62,15 +73,18 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.bottom_nav_home:
viewPager.setCurrentItem(0);
case R.id.bottom_nav_events:
viewPager.setCurrentItem(ListFragment.PageType.EVENTS);
return true;
case R.id.bottom_nav_notifications:
viewPager.setCurrentItem(ListFragment.PageType.NOTIFICATIONS);
return true;
case R.id.bottom_nav_jobs:
viewPager.setCurrentItem(ListFragment.PageType.JOBS);
return true;
/*case R.id.bottom_nav_blitz:
viewPager.setCurrentItem(1);
viewPager.setCurrentItem(3);
return true;*/
case R.id.bottom_nav_events:
viewPager.setCurrentItem(1);
return true;
}
return false;
}
......@@ -124,6 +138,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
viewPager = findViewById(R.id.viewPager);
viewPager.setAdapter(pagerAdapter);
viewPager.setPageTransformer(true, new DepthPageTransformer()); //used for animating
viewPager.setOffscreenPageLimit(ListFragment.PageType.COUNT);//prevent pages being deleted when we swipe to far
//set for the bottom nav to be updated when we swipe to change the page
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
......@@ -133,12 +148,14 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
@Override
public void onPageSelected(int position) {
//need to convert index to resource id
if(position == 0)
position = R.id.bottom_nav_home;
if(position == ListFragment.PageType.EVENTS)
position = R.id.bottom_nav_events;
/*else if (position == 1)
position = R.id.bottom_nav_blitz;*/
else if (position == 1)
position = R.id.bottom_nav_events;
else if (position == ListFragment.PageType.NOTIFICATIONS)
position = R.id.bottom_nav_notifications;
else if (position == ListFragment.PageType.JOBS)
position = R.id.bottom_nav_jobs;
bottomNavigation.setSelectedItemId(position);
}
......@@ -160,7 +177,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
UserInfo.current = null;
Events.ClearSignups();
pagerAdapter.RefreshCurrentList(true);
pagerAdapter.RefreshPage(ListFragment.PageType.EVENTS, true);
SetLoginUIDirty();
System.gc();//run garbage collector explicitly to clean up user data
......@@ -216,6 +233,14 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
startActivityForResult(intent, 0);
}
public void StartJobDetailActivity(int jobGroup, int jobIndex)
{
Intent intent = new Intent(this, JobDetailActivity.class);
intent.putExtra("jobGroup", jobGroup);
intent.putExtra("jobIndex", jobIndex);
startActivityForResult(intent, 0);
}
private void StartCheckinActivity() {
Intent intent = new Intent(this, ch.amiv.android_app.checkin.MainActivity.class);
startActivity(intent);
......@@ -335,7 +360,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
* This will handle changing between the pages
*/
public class PagerAdapter extends FragmentPagerAdapter {
ListFragment currentFragment;
int currentPosition;
private ListFragment[] pages = new ListFragment[ListFragment.PageType.COUNT];
public PagerAdapter(FragmentManager fm) {
super(fm);
......@@ -343,27 +369,28 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
@Override
public int getCount() {
return 2;
return ListFragment.PageType.COUNT;
}
@Override
public Fragment getItem(int position) {
return ListFragment.NewInstance(position);
if(pages[position] == null)
pages[position] = ListFragment.NewInstance(position);
return pages[position];
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
if (currentFragment != object) {
currentFragment = ((ListFragment) object);
}
currentPosition = position;
super.setPrimaryItem(container, position, object);
}
public void RefreshCurrentList(boolean animate)
{
if(currentFragment != null)
currentFragment.RefreshList(animate);
public void RefreshPage(int position, boolean animate){
if(pages[position] != null)
pages[position].RefreshList(animate);
else
Log.e("pageview", "RefreshPage(), Page does not exist, will not refresh: " + position);
}
}
......
......@@ -29,6 +29,9 @@ import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import ch.amiv.android_app.events.Events;
import ch.amiv.android_app.jobs.Jobs;
public final class Requests {
private static RequestQueue requestQueue;
private static ImageLoader imageLoader;
......@@ -229,6 +232,90 @@ public final class Requests {
RunCallback(errorCallback);
}
public static void FetchJobList(final Context context, final OnDataReceivedCallback callback, final OnDataReceivedCallback errorCallback, @NonNull final String jobId)
{
if(!CheckConnection(context)) {
RunCallback(errorCallback);
return;
}
String url = Settings.API_URL + "joboffers" + (jobId.isEmpty() ? "" : "/" + jobId);
Log.e("request", "url: " + url);
StringRequest request = new StringRequest(Request.Method.GET, url,null, null)
{
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) { //Note: the parseNetworkResponse is only called if the response was successful (codes 2xx), else parseNetworkError is called.
if(response != null) {
Log.e("request", "fetch jobs status Code: " + response.statusCode);
try {
final JSONObject json = new JSONObject(new String(response.data));
//Update events on main thread
if(callback != null) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
if(jobId.isEmpty())
Jobs.UpdateJobInfos(json.getJSONArray("_items"));
else
Jobs.UpdateSingleJob(json, jobId);
} catch (JSONException e) {
e.printStackTrace();
}
callback.OnDataReceived();
}
};
callbackHandler.post(runnable);
}
Log.e("request", new JSONObject(new String(response.data)).toString());
} catch (JSONException e) {
RunCallback(errorCallback);
e.printStackTrace();
}
}
else {
RunCallback(errorCallback);
Log.e("request", "Request returned null response. fetch jobs");
}
return super.parseNetworkResponse(response);
}
@Override
protected VolleyError parseNetworkError(final VolleyError volleyError) {
if(volleyError != null && volleyError.networkResponse != null)
Log.e("request", "status code: " + volleyError.networkResponse.statusCode + "\n" + new String(volleyError.networkResponse.data));
else
Log.e("request", "Request returned null response. fetch jobs");
RunCallback(errorCallback);
return super.parseNetworkError(volleyError);
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
if(Settings.IsLoggedIn(context)) {
Map<String,String> headers = new HashMap<String, String>();
String credentials = Settings.GetToken(context) + ":";
String auth = "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
headers.put("Authorization", auth);
return headers;
}
return super.getHeaders();
}
};
//send the request and check if it failed
if(!Requests.SendRequest(request, context))
RunCallback(errorCallback);
}
/**
* Will fetch the user from the api if we have an access token. ie Token -> User. Data is stored in the current userinfo (UserInfo.current). Overwrites the current user info if it exists
*/
......
......@@ -11,11 +11,17 @@ public class UserInfo {
public String session_etag = "";
public String nethz = "";
public String legi = "";
public String rfid = "";
public String email = "";
public String firstname = "";
public String lastname = "";
public String email = "";
public String membership = "";
public String gender = "";
public String phone = "";
public String department = "";
public boolean send_newsletter = true;
private UserInfo(JSONObject json, boolean isFromTokenRequest)
{
......@@ -40,6 +46,8 @@ public class UserInfo {
if (json.has("legi"))
legi = json.getString("legi");
if (json.has("rfid"))
rfid = json.getString("rfid");
if (json.has("firstname"))
firstname = json.getString("firstname");
if (json.has("lastname"))
......@@ -48,10 +56,17 @@ public class UserInfo {
nethz = json.getString("nethz");
if (json.has("email"))
email = json.getString("email");
if (json.has("membership"))
membership = json.getString("membership");
if (json.has("gender"))
gender = json.getString("gender");
if (json.has("phone"))
phone = json.getString("phone");
if (json.has("department"))
department = json.getString("department");
if (json.has("send_newsletter"))
send_newsletter = json.getBoolean("send_newsletter");
}
catch (JSONException e) {
e.printStackTrace();
......
package ch.amiv.android_app.demo;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.util.Base64;
import android.util.Log;
import com.android.volley.AuthFailureError;