Commit 85f7bcc9 authored by Roger Barton's avatar Roger Barton
Browse files

Merge branch 'Joboffers'

parents 1780c988 0902a45c
Pipeline #4611 failed with stages
......@@ -10,6 +10,7 @@
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name=".core.MyApplication"
......@@ -50,7 +51,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;
/**
* A class to simplify refreshsing a recyclerview, used in events and jobs list for example
*/
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,27 @@ 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
* This class is a fragment for a list screen used in the main activity by the page viewer for events, jobs, it will use the given page position to tell which one it is
*/
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;
}
private RecyclerView recyclerView;
private BaseRecyclerAdapter recyclerAdapter;
private RecyclerView.LayoutManager recyclerLayoutAdapter;
SwipeRefreshLayout swipeRefreshLayout;
Requests.OnDataReceivedCallback cancelRefreshCallback = new Requests.OnDataReceivedCallback() {
private SwipeRefreshLayout swipeRefreshLayout;
private Requests.OnDataReceivedCallback cancelRefreshCallback = new Requests.OnDataReceivedCallback() {
@Override
public void OnDataReceived() {
swipeRefreshLayout.setRefreshing(false);
......@@ -52,31 +61,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 +119,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);
}
recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
if(pagePosition == PageType.EVENTS)
recyclerAdapter = new EventsListAdapter(getActivity());
else if (pagePosition == PageType.JOBS)
recyclerAdapter = new JobListAdapter(getActivity());
}
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 +171,8 @@ public class ListFragment extends Fragment {
public void onResume() {
super.onResume();
if(recylcerAdaper != null)
recylcerAdaper.RefreshData();
if(recyclerAdapter != null)
recyclerAdapter.RefreshData();
}
/**
......@@ -147,6 +181,9 @@ public class ListFragment extends Fragment {
*/
public void AnimateList(View view)
{
if(recyclerAdapter == null)
return;
getActivity().runOnUiThread(new Runnable() {
public void run() {
recyclerView.invalidate();
......
......@@ -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);
}
......@@ -149,6 +166,14 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
}
//endregion
@Override
protected void onPostResume() {
super.onPostResume();
if(pagerAdapter != null)
pagerAdapter.notifyDataSetChanged();
}
//region - ====Login====
/**
* Log the user out, delete token. Will not log the user out of the device with the API as this is used with other amiv services as well
......@@ -160,7 +185,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 +241,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);
......@@ -331,39 +364,43 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
//region =====START OF PAGEVIEW==============
protected ListFragment[] pages = new ListFragment[ListFragment.PageType.COUNT];
/**
* This will handle changing between the pages
*/
public class PagerAdapter extends FragmentPagerAdapter {
ListFragment currentFragment;
int currentPosition;
public PagerAdapter(FragmentManager fm) {
super(fm);
for(int i = 0; i< ListFragment.PageType.COUNT; i++)
pages[i] = ListFragment.NewInstance(i);
}
@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,9 +29,13 @@ 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;
private static final int MAX_CACHED_IMAGES = 50;
public static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
......@@ -73,7 +77,9 @@ public final class Requests {
return;
}
String url = Settings.API_URL + "events" + (eventId.isEmpty() ? "" : "/" + eventId);
String url = Settings.API_URL + "events" + (eventId.isEmpty() ?
(Settings.showHiddenFeatures ? "" : "?where={\"show_website\":true}")
: "/" + eventId) ;
Log.e("request", "url: " + url);
StringRequest request = new StringRequest(Request.Method.GET, url,null, null)
......@@ -229,6 +235,92 @@ 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() ?
(Settings.showHiddenFeatures ? "" : "?where={\"show_website\":true}")
: "/" + 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
*/
......@@ -382,7 +474,7 @@ public final class Requests {
requestQueue = Volley.newRequestQueue(context);
imageLoader = new ImageLoader(requestQueue, new ImageLoader.ImageCache() {
private final LruCache<String, Bitmap> mCache = new LruCache<String, Bitmap>(10);
private final LruCache<String, Bitmap> mCache = new LruCache<>(MAX_CACHED_IMAGES);
public void putBitmap(String url, Bitmap bitmap) {
mCache.put(url, bitmap);
}
......
......@@ -18,6 +18,9 @@ public class Settings {
//public static final String API_URL = "http://192.168.1.105:5000/";
public static final String API_URL = "https://api-dev.amiv.ethz.ch/";
//Whether to show hidden events, where the adverts should not have started yet, should later be set by user access group
public static final boolean showHiddenFeatures = true;
//Vars for saving/reading the url from shared prefs, to allow saving between sessions. For each variable, have a key to access it and a default value
private static SharedPreferences sharedPrefs;
private static final String SHARED_PREFS_KEY = "ch.amiv.android_app";
......
......@@ -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"</