Commit 0bdaf281 authored by Roger Barton's avatar Roger Barton
Browse files

Better fix for page view, added saving of userinfo between sessions

parent 337c18f6
......@@ -22,6 +22,7 @@ import ch.amiv.android_app.jobs.JobListAdapter;
/**
* 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
* NOTE: This fragment will lose its connection to the parent activity, when the app is resumed, use MainActivity.instance as the activity and context
*/
public class ListFragment extends Fragment {
private int pagePosition; //the fragments page in the pageview of the main activity
......@@ -47,7 +48,7 @@ public class ListFragment extends Fragment {
public Requests.OnDataReceivedCallback onEventsListUpdatedCallback = new Requests.OnDataReceivedCallback() {
@Override
public void OnDataReceived() {
Requests.FetchEventSignups(recyclerView.getContext(), onSignupsUpdatedCallback, null, "");
Requests.FetchEventSignups(MainActivity.instance, onSignupsUpdatedCallback, null, "");
RefreshList(true);
}
};
......@@ -97,29 +98,29 @@ public class ListFragment extends Fragment {
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if(!(recyclerView.getContext() instanceof MainActivity))
if(!(MainActivity.instance instanceof MainActivity))
return;
if(pagePosition == PageType.EVENTS)
Requests.FetchEventList(recyclerView.getContext(), onEventsListUpdatedCallback, cancelRefreshCallback, "");
Requests.FetchEventList(MainActivity.instance, onEventsListUpdatedCallback, cancelRefreshCallback, "");
else if (pagePosition == PageType.JOBS)
Requests.FetchJobList(recyclerView.getContext(), onJobsListUpdatedCallback, cancelRefreshCallback, "");
Requests.FetchJobList(MainActivity.instance, onJobsListUpdatedCallback, cancelRefreshCallback, "");
}
});
//refresh on activity start
swipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
if(!(recyclerView.getContext() instanceof MainActivity))
if(!(MainActivity.instance instanceof MainActivity))
return;
if(pagePosition == PageType.EVENTS) {
SetRefreshUI(true);
Requests.FetchEventList(recyclerView.getContext(), onEventsListUpdatedCallback, cancelRefreshCallback, "");
Requests.FetchEventList(MainActivity.instance, onEventsListUpdatedCallback, cancelRefreshCallback, "");
}
else if(pagePosition == PageType.JOBS){
SetRefreshUI(true);
Requests.FetchJobList(recyclerView.getContext(), onJobsListUpdatedCallback, cancelRefreshCallback, "");
Requests.FetchJobList(MainActivity.instance, onJobsListUpdatedCallback, cancelRefreshCallback, "");
}
}
});
......@@ -130,17 +131,17 @@ public class ListFragment extends Fragment {
recyclerView.setHasFixedSize(true);
// use a linear layout manager
recyclerLayoutAdapter = new LinearLayoutManager(recyclerView.getContext());
recyclerLayoutAdapter = new LinearLayoutManager(MainActivity.instance);
recyclerView.setLayoutManager(recyclerLayoutAdapter);
// specify an adapter (see also next example)
if(pagePosition == PageType.EVENTS)
recyclerAdapter = new EventsListAdapter(((Activity) recyclerView.getContext()));
recyclerAdapter = new EventsListAdapter(((Activity) MainActivity.instance));
else if (pagePosition == PageType.JOBS)
recyclerAdapter = new JobListAdapter((Activity) recyclerView.getContext());
recyclerAdapter = new JobListAdapter((Activity) MainActivity.instance);
if(recyclerAdapter != null) {
recyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(recyclerView.getContext(), R.anim.layout_anim_falldown));
recyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(MainActivity.instance, R.anim.layout_anim_falldown));
recyclerView.setAdapter(recyclerAdapter);
AnimateList(null);
......@@ -188,6 +189,9 @@ public class ListFragment extends Fragment {
if(recyclerAdapter != null)
recyclerAdapter.RefreshData();
//Reconnect the fragment to the mainactivity
MainActivity.instance.pagerAdapter.ReconnectFragment(this, pagePosition);
}
private void SetRefreshUI(boolean isRefreshing){
......
......@@ -79,10 +79,17 @@ public class LoginActivity extends AppCompatActivity {
final String password = passwordField.getText().toString();
if(username.isEmpty()) {
Snackbar.make(submitButton, R.string.snack_fill_all_fields, Snackbar.LENGTH_SHORT).show();
Snackbar.make(submitButton, R.string.snack_fill_fields, Snackbar.LENGTH_SHORT).show();
return;
}
if(password.isEmpty()){
UserInfo.SetEmailOnlyLogin(getApplicationContext(), username, false);
SetSubmitButtonState(false, true);
Settings.Vibrate(Settings.VibrateTime.NORMAL, getApplicationContext());
ReturnToCallingActivity(true);
}
//Does a POST request to sessions to create a session and get a token. Does *not* use the OAuth process, see api docs
StringRequest request = new StringRequest(Request.Method.POST, Settings.API_URL + "sessions", null, null)
{
......@@ -110,7 +117,7 @@ public class LoginActivity extends AppCompatActivity {
submitButton.post(new Runnable() {
@Override
public void run() {
UserInfo.UpdateCurrent(json, true);
UserInfo.UpdateCurrent(getApplicationContext(), json, true, false);
}
});
}
......
......@@ -34,6 +34,8 @@ import ch.amiv.android_app.util.PersistentStorage;
* This is the first screen. features: drawer, pageview with bottom navigation bar and within each page a list view.
*/
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
public static MainActivity instance;
//region - ====Variables====
private NavigationView drawerNavigation;
......@@ -43,7 +45,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
private BottomNavigationView bottomNavigation;
private ViewPager viewPager;
private PagerAdapter pagerAdapter;
public PagerAdapter pagerAdapter;
/**
* Handle what should happen when the bottom nav buttons are pressed, will change the page of the viewpager
......@@ -76,6 +78,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
instance = this;
setContentView(R.layout.core_activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
......@@ -98,8 +101,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
new Settings(getApplicationContext()); //creates the settings instance, so we can store/retrieve shared preferences
//fetch the user info if we are logged in, there exists a token from the previous session, should be cached.
if(Settings.IsLoggedIn(getApplicationContext()) && (UserInfo.current == null || UserInfo.current.nethz.isEmpty()))
{
if(!PersistentStorage.LoadUserInfo(getApplicationContext()) || UserInfo.current._id.isEmpty() && !Settings.IsEmailOnlyLogin(getApplicationContext())) {
Requests.FetchUserData(getApplicationContext(), drawerNavigation, new Requests.OnDataReceivedCallback() {
@Override
public void OnDataReceived() {
......@@ -153,16 +156,21 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
*/
public void LogoutUser()
{
if(Settings.IsEmailOnlyLogin(getApplicationContext())) {
Settings.SetToken("", getApplicationContext());
}
else {
//delete session at the server and then clear the token
Requests.DeleteCurrentSession(getApplicationContext());
Events.ClearSignups();
}
PersistentStorage.ClearUser(getApplicationContext());
UserInfo.current = null;
Events.ClearSignups();
pagerAdapter.RefreshPage(ListFragment.PageType.EVENTS, true);
SetLoginUIDirty();
System.gc();//run garbage collector explicitly to clean up user data
Requests.FetchEventList(getApplicationContext(), onEventsListUpdatedCallback, null, "");
Requests.FetchEventList(getApplicationContext(), pages.get(ListFragment.PageType.EVENTS).onEventsListUpdatedCallback, null, "");
}
/**
......@@ -176,10 +184,16 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
.setChecked(false);
if(UserInfo.current != null)
{
if(Settings.IsEmailOnlyLogin(getApplicationContext())) {
drawer_title.setText(UserInfo.current.email);
drawer_subtitle.setText(R.string.email_only_login);
}
else {
drawer_title.setText(UserInfo.current.firstname + " " + UserInfo.current.lastname);
drawer_subtitle.setText(UserInfo.current.email);
}
}
}
else
{
drawerNavigation.getMenu().findItem(R.id.nav_login)
......@@ -245,28 +259,27 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 0) {
if (resultCode == RESULT_OK) {
if (requestCode == 0 && resultCode == RESULT_OK) {
// If we are returning from the login activity and have had a successfully login, refresh the user info and login UI
boolean refreshLogin = data.getBooleanExtra("login_success", false);
if(refreshLogin && Settings.IsLoggedIn(getApplicationContext()))
{
SetLoginUIDirty();
Requests.FetchUserData(getApplicationContext(), drawerNavigation, new Requests.OnDataReceivedCallback() {
@Override
public void OnDataReceived() {
SetLoginUIDirty();
//Update events and signups with the new userinfo
if(Events.eventInfos.size() > 0)
Requests.FetchEventSignups(getApplicationContext(), onSignupsUpdatedCallback, null, "");
Requests.FetchEventSignups(getApplicationContext(), pages.get(ListFragment.PageType.EVENTS).onEventsListUpdatedCallback, null, "");
else
Requests.FetchEventList(getApplicationContext(), onEventsListUpdatedCallback, null, "");
Requests.FetchEventList(getApplicationContext(), pages.get(ListFragment.PageType.EVENTS).onEventsListUpdatedCallback, null, "");
}
});
}
}
}
}
//region =====TOOLBAR=====
@Override
......@@ -392,6 +405,20 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
else
Log.e("pageview", "RefreshPage(), Page does not exist will not refresh: " + position);
}
/**
* Used to reconnect the link to the fragment in onresume
* @param fragment
* @param position
*/
public void ReconnectFragment(ListFragment fragment, int position){
try {
pages.set(position, fragment);
}
catch (Exception e){
e.printStackTrace();
}
}
}
/**
......
package ch.amiv.android_app.core;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
......@@ -138,7 +137,7 @@ public final class Requests {
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
if(Settings.IsLoggedIn(context)) {
if(Settings.HasToken(context)) {
Map<String,String> headers = new HashMap<String, String>();
String credentials = Settings.GetToken(context) + ":";
......@@ -163,7 +162,7 @@ public final class Requests {
*/
public static void FetchEventSignups(final Context context, final OnDataReceivedCallback callback, final OnDataReceivedCallback errorCallback, @NonNull String eventId)
{
if(!Settings.IsLoggedIn(context) || UserInfo.current == null || UserInfo.current._id.isEmpty()) {
if(!Settings.HasToken(context) || UserInfo.current == null || UserInfo.current._id.isEmpty()) {
RunCallback(errorCallback);
return;
}
......@@ -303,7 +302,7 @@ public final class Requests {
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
if(Settings.IsLoggedIn(context)) {
if(Settings.HasToken(context)) {
Map<String,String> headers = new HashMap<String, String>();
String credentials = Settings.GetToken(context) + ":";
......@@ -327,7 +326,7 @@ public final class Requests {
*/
public static void FetchUserData(final Context context, final View view, final OnDataReceivedCallback callback)
{
if(!Settings.IsLoggedIn(context) || !CheckConnection(context))
if(!Settings.HasToken(context) || !CheckConnection(context))
return;
//Do request Token->User
......@@ -344,9 +343,7 @@ public final class Requests {
callbackHandler.post(new Runnable() {
@Override
public void run() {
UserInfo.UpdateCurrent(json, false);
/*UserInfo user = new UserInfo(json, false);
user.SetAsCurrent();*/
UserInfo.UpdateCurrent(context, json, false, false);
}
});
......@@ -398,11 +395,14 @@ public final class Requests {
*/
public static void DeleteCurrentSession(final Context context)
{
if(Settings.IsEmailOnlyLogin(context))
return;
final UserInfo user = UserInfo.current;
final String token = Settings.GetToken(context);
Settings.SetToken("", context);
if(!Settings.IsLoggedIn(context) || UserInfo.current == null || UserInfo.current.session_id.isEmpty())
if(!Settings.HasToken(context) || UserInfo.current == null || UserInfo.current.session_id.isEmpty())
return;
//Do request Token->User
......
......@@ -5,8 +5,6 @@ import android.content.SharedPreferences;
import android.os.Vibrator;
import android.util.Log;
import java.security.PublicKey;
/**
* This class is used to save settings so they can be restored in another session later.
* Use this class to retrieve visible/hidden settings.
......@@ -23,7 +21,7 @@ public class Settings {
//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";
public static final String SHARED_PREFS_KEY = "ch.amiv.android_app";
private static final String apiUrlPrefKey = "ch.amiv.android_app.serverurl";
private static final String defaultApiUrl = "https://api-dev.amiv.ethz.ch";
private static final String themeKey = "ch.amiv.android_app.theme";
......@@ -103,12 +101,27 @@ public class Settings {
* Note: will only check if a token exists. This token may have expired but not have been refreshed/deleted.
* @return True if the user is logged into the api and has an access token.
*/
public static boolean IsLoggedIn(Context context){
public static boolean HasToken(Context context){
CheckInitSharedPrefs(context);
String t = sharedPrefs.getString(apiTokenKey, "");
return !t.isEmpty();
}
/**
* Will return whether the user is only loggedd in with an email, if they do not have an api login, false if current user has not be initialised
*/
public static boolean IsEmailOnlyLogin(Context context){
return !Settings.HasToken(context) && UserInfo.current != null && !UserInfo.current.email.isEmpty();
}
/**
* Note: will only check if a token exists. This token may have expired but not have been refreshed/deleted.
* @return True if the user is logged into the api and has an access token.
*/
public static boolean IsLoggedIn(Context context){
return HasToken(context) || IsEmailOnlyLogin(context);
}
// Theme
public static void SetIsDarkTheme(boolean value, Context context) {
......
package ch.amiv.android_app.core;
import android.content.Context;
import org.json.JSONException;
import org.json.JSONObject;
public class UserInfo {
import java.io.Serializable;
import java.util.EmptyStackException;
import java.util.zip.CheckedOutputStream;
import ch.amiv.android_app.util.PersistentStorage;
public class UserInfo implements Serializable{
public static UserInfo current;
public String _id = "";
......@@ -22,12 +30,23 @@ public class UserInfo {
public String department = "";
public boolean send_newsletter = true;
private UserInfo(JSONObject json, boolean isFromTokenRequest)
{
Update(json, isFromTokenRequest);
}
private UserInfo (String email){
Update(email);
}
/**
* Used when the user only logs in with an email
* @param email_
*/
public void Update(String email_){
email = email_;
}
/**
* Use this to update the user info with a json. Can handle partial update where old items will not be overwritten if they do not exist
* @param isFromTokenRequest set to true when the json is from a /sessions POST request, where the user ID is "user" not "_id"
......@@ -38,10 +57,15 @@ public class UserInfo {
if (json.has(isFromTokenRequest ? "user" : "_id"))
_id = json.getString(isFromTokenRequest ? "user" : "_id");
if(isFromTokenRequest && json.has("_etag"))
if(json.has("session_etag"))
session_etag = json.getString("session_etag"); //if from a gson saved locally
else if(isFromTokenRequest && json.has("_etag"))
session_etag = json.getString("_etag");
if(isFromTokenRequest && json.has("_id"))
if(json.has("session_id"))
session_id = json.getString("session_id");
else if(isFromTokenRequest && json.has("_id"))
session_id = json.getString("_id");
if (json.has("legi"))
......@@ -77,15 +101,25 @@ public class UserInfo {
* Will safely update the current user or create it
* @param isFromTokenRequest This ensures the correct values are retrieved from the json. Set to true when the json is from a /sessions POST request, where the user ID is "user" not "_id" as usually
*/
public static void UpdateCurrent(JSONObject json, boolean isFromTokenRequest){
public static void UpdateCurrent(Context context, JSONObject json, boolean isFromTokenRequest, boolean isSavedInstance){
if(current != null)
current.Update(json, isFromTokenRequest);
else {
current = new UserInfo(json, isFromTokenRequest);
}
if(!isSavedInstance)
PersistentStorage.SaveUserInfo(context);
}
public static void SetEmailOnlyLogin(Context context, String email, boolean isSavedInstance){
if(current != null)
current.Update(email);
else {
current = new UserInfo(email);
}
public void SetAsCurrent(){
current = this;
if(!isSavedInstance)
PersistentStorage.SaveUserInfo(context);
}
}
......@@ -46,7 +46,7 @@ public class MainActivity extends AppCompatActivity {
Map<String,String> headers = new HashMap<String, String>();
//This adds basic auth using our token if it exists, this can be retrieved from core.Settings
if(Settings.IsLoggedIn(getApplicationContext())) {
if(Settings.HasToken(getApplicationContext())) {
String credentials = Settings.GetToken(getApplicationContext()) + ":";
String auth = "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
headers.put("Authorization", auth);
......
......@@ -114,15 +114,20 @@ public class EventDetailActivity extends AppCompatActivity {
boolean refreshLogin = data.getBooleanExtra("login_success", false);
if(refreshLogin && Settings.IsLoggedIn(getApplicationContext()))
{
if(Settings.IsEmailOnlyLogin(getApplicationContext()))
Snackbar.make(posterImage, R.string.requires_login, Snackbar.LENGTH_SHORT).show();
else {
Requests.FetchEventSignups(getApplicationContext(), new Requests.OnDataReceivedCallback() {
@Override
public void OnDataReceived() {
UpdateRegisterButton();
}
}, null, "");
RegisterForEvent(null);
}
responseIntent.putExtra("login_success", refreshLogin);
ScrollToBottom(null);
RegisterForEvent(null);
}
}
}
......@@ -312,7 +317,7 @@ public class EventDetailActivity extends AppCompatActivity {
}
//Redirect to the login page first
if(!Settings.IsLoggedIn(getApplicationContext())){
if(!Settings.HasToken(getApplicationContext()) && !event().allow_email_signup){
Intent intent = new Intent(this, LoginActivity.class);
intent.putExtra("cause", "register_event");
startActivityForResult(intent, 0);
......@@ -386,9 +391,11 @@ public class EventDetailActivity extends AppCompatActivity {
Map<String,String> headers = new HashMap<String, String>();
// Add basic auth with token
if(Settings.HasToken(getApplicationContext())) {
String credentials = Settings.GetToken(getApplicationContext()) + ":";
String auth = "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
headers.put("Authorization", auth);
}
return headers;
}
......@@ -396,6 +403,9 @@ public class EventDetailActivity extends AppCompatActivity {
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<String, String>();
params.put("event", event()._id);
if(Settings.IsEmailOnlyLogin(getApplicationContext()))
params.put("email", UserInfo.current.email);
else
params.put("user", UserInfo.current._id);
return params;
}
......@@ -421,6 +431,9 @@ public class EventDetailActivity extends AppCompatActivity {
if(event().time_register_end.after(today))
{
registerButton.setEnabled(true);
if(Settings.IsEmailOnlyLogin(getApplicationContext()) && !event().allow_email_signup)
registerButton.setText(R.string.requires_login);
else
registerButton.setText(R.string.register_title);
}
else
......@@ -433,7 +446,6 @@ public class EventDetailActivity extends AppCompatActivity {
registerButton.setEnabled(false);
registerButton.setText(R.string.register_soon);
}
}
public void ScrollToTop (View view) {
......
......@@ -6,6 +6,7 @@ import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
......@@ -20,7 +21,7 @@ import static ch.amiv.android_app.util.Util.BuildFileUrl;
/**
* This is all the data about one event AND the current users signup data about that event
*/
public class EventInfo {
public class EventInfo implements Serializable{
//region - ====Variables====
public String _id;
public String _etag;
......@@ -39,8 +40,8 @@ public class EventInfo {
public int signup_count;
public int spots;
public String allow_email_signup;
public String show_website;
public boolean allow_email_signup;
public boolean show_website;
//Media
public String poster_url;
......@@ -95,8 +96,8 @@ public class EventInfo {
signup_count = json.optInt("signup_count");
spots = json.optInt("spots");
allow_email_signup = json.optString("allow_email_signup");
show_website = json.optString("show_website");
allow_email_signup = json.optBoolean("allow_email_signup", false);
show_website = json.optBoolean("show_website", false);
//Add dates
String _start = json.optString("time_start");
......
package ch.amiv.android_app.util;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.SharedPreferences;
import android.os.Environment;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;