Commit fdd64418 authored by Roger Barton's avatar Roger Barton
Browse files

Finished app intro, Refactored shared prefs

SettingsActivity is partially broken, 
Access a shared pref with its key and the Get/SetPref fct
Added editing & patch request for rfid, 
Changed strings to use translatable attribute
parent c7c602eb
"$schema":"http://json-schema.org/draft-04/schema#"
"additionalProperties":false
"title":"Additional Fields"
"type":"object"
"properties":{
"SBB_Abo":
{
"type":"string"
"enum":["None", "GA", "Halbtax", "Gleis 7"]
}
"Food":
{
"type":"string"
"enum":["Omnivor", "Vegi", "Vegan", "Other"]
}
"Special Food Requirements":
{
"type":"string"
}
}
"required":["SBB_Abo", "Food"]
...@@ -2,6 +2,7 @@ package ch.amiv.android_app.core; ...@@ -2,6 +2,7 @@ package ch.amiv.android_app.core;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.view.PagerAdapter; import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
...@@ -12,11 +13,14 @@ import android.view.LayoutInflater; ...@@ -12,11 +13,14 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import ch.amiv.android_app.R; import ch.amiv.android_app.R;
import ch.amiv.android_app.ui.NonSwipeableViewPager; import ch.amiv.android_app.ui.NonSwipeableViewPager;
import ch.amiv.android_app.ui.PrefDetailView;
import ch.amiv.android_app.util.Util;
public class IntroActivity extends AppCompatActivity { public class IntroActivity extends AppCompatActivity {
...@@ -25,19 +29,39 @@ public class IntroActivity extends AppCompatActivity { ...@@ -25,19 +29,39 @@ public class IntroActivity extends AppCompatActivity {
private LinearLayout dotsLayout; private LinearLayout dotsLayout;
private TextView[] dots; private TextView[] dots;
private Button btnSkip, btnNext; private Button btnSkip, btnNext;
private EditText rfidField;
//page configs & layouts //page configs & layouts
private int[] layouts = { private int[] layouts = {
R.layout.core_intro_slide_language, R.layout.core_intro_slide_language,
R.layout.core_intro_slide_info, R.layout.core_intro_slide_info,
R.layout.core_intro_slide_profile, R.layout.core_intro_slide_profile,
R.layout.core_intro_slide_event_prefs}; R.layout.core_intro_slide_event_prefs,
private boolean[] allowSkip = {false, false, true, false}; R.layout.core_intro_slide_pref_detail};//Used for editing an enum pref
private int[] nextText = {0, R.string.next, 0, R.string.lets_go};//set to 0 to hide next button
private static final class Page {
private static final int LANGUAGE = 0;
private static final int APP_INFO = 1;
private static final int EDIT_PROFILE = 2;
private static final int EVENT_PREF = 3;
private static final int PREF_DETAIL = 4;
}
private boolean[] allowSkip = {false, false, true, false, false};
private int[] nextText = {0, R.string.next, R.string.next, R.string.lets_go, 0};//set to 0 to hide next button
private boolean hasLoggedIn; //used when using back after the login page private boolean hasLoggedIn; //used when using back after the login page
private String langSetIntentKey = "lang_set"; private String langSetIntentKey = "lang_set";
private View.OnClickListener onNextClick = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (viewPager.getCurrentItem() == 2)//If we press next on the profile page, submit new data
UpdateProfile();
NextPage(true);
}
};
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
...@@ -47,6 +71,7 @@ public class IntroActivity extends AppCompatActivity { ...@@ -47,6 +71,7 @@ public class IntroActivity extends AppCompatActivity {
dotsLayout = findViewById(R.id.layoutDots); dotsLayout = findViewById(R.id.layoutDots);
btnSkip = findViewById(R.id.buttonSkip); btnSkip = findViewById(R.id.buttonSkip);
btnNext = findViewById(R.id.buttonNext); btnNext = findViewById(R.id.buttonNext);
rfidField = findViewById(R.id.rfidField);
boolean hasSetLang = false; boolean hasSetLang = false;
Intent intent = getIntent(); Intent intent = getIntent();
...@@ -66,12 +91,7 @@ public class IntroActivity extends AppCompatActivity { ...@@ -66,12 +91,7 @@ public class IntroActivity extends AppCompatActivity {
} }
}); });
btnNext.setOnClickListener(new View.OnClickListener() { btnNext.setOnClickListener(onNextClick);
@Override
public void onClick(View v) {
NextPage(true);
}
});
if(hasSetLang) if(hasSetLang)
NextPage(true); NextPage(true);
...@@ -82,17 +102,17 @@ public class IntroActivity extends AppCompatActivity { ...@@ -82,17 +102,17 @@ public class IntroActivity extends AppCompatActivity {
@Override @Override
public void onBackPressed() { public void onBackPressed() {
int currentPos = viewPager.getCurrentItem(); int currentPos = viewPager.getCurrentItem();
if(currentPos == 2) { if(currentPos == Page.EDIT_PROFILE) {
StartLoginActivity(true); StartLoginActivity(true);
} }
else if(currentPos == 3){ else if(currentPos == Page.EVENT_PREF){
if(hasLoggedIn) if(hasLoggedIn)
viewPager.setCurrentItem(2); SetPage(Page.EDIT_PROFILE, true);
else else
StartLoginActivity(true); StartLoginActivity(true);
} }
else if(currentPos > 0) else if(currentPos > 0)
viewPager.setCurrentItem(currentPos -1); SetPage(currentPos -1, true);
//Dont call super.onBackPressed as we will otherwise leave the app //Dont call super.onBackPressed as we will otherwise leave the app
} }
...@@ -113,7 +133,10 @@ public class IntroActivity extends AppCompatActivity { ...@@ -113,7 +133,10 @@ public class IntroActivity extends AppCompatActivity {
* Will update the page dots at the bottom, to indicate which page we are on. note: the views are deleted and recreated * Will update the page dots at the bottom, to indicate which page we are on. note: the views are deleted and recreated
*/ */
private void RefreshPageDots(int currentPage) { private void RefreshPageDots(int currentPage) {
dots = new TextView[layouts.length +1]; //+1 for login acitivty if(currentPage > layouts.length -2)//pref detail view
return;
dots = new TextView[layouts.length]; //+1 for login acitivty, -1 for pref detail
int inactive = ContextCompat.getColor(this, R.color.darkGrey); int inactive = ContextCompat.getColor(this, R.color.darkGrey);
dotsLayout.removeAllViews(); dotsLayout.removeAllViews();
...@@ -126,17 +149,40 @@ public class IntroActivity extends AppCompatActivity { ...@@ -126,17 +149,40 @@ public class IntroActivity extends AppCompatActivity {
} }
if (dots.length > 0)//change indexes for login activity if (dots.length > 0)//change indexes for login activity
dots[currentPage < 2 ? currentPage : currentPage +1].setTextColor(ContextCompat.getColor(this, R.color.lightGrey)); dots[currentPage < Page.EDIT_PROFILE ? currentPage : currentPage +1].setTextColor(ContextCompat.getColor(this, R.color.lightGrey));
} }
private void NextPage(boolean success){ private void NextPage(boolean success){
int nextPos = viewPager.getCurrentItem() + 1; int currentPos = viewPager.getCurrentItem();
if (nextPos == 2)//go to login first if(currentPos == Page.EDIT_PROFILE)
Util.HideKeyboard(this);
if (currentPos == Page.APP_INFO)//go to login first
StartLoginActivity(false); StartLoginActivity(false);
else if (nextPos < layouts.length) { else if (currentPos == Page.EVENT_PREF)
viewPager.setCurrentItem(nextPos);
} else {
StartMainAcivity(); StartMainAcivity();
else if (currentPos +1 < layouts.length)
SetPage(currentPos +1, true);
}
private void SetPage(int page, boolean setupPage){
viewPager.setCurrentItem(page);//This actually changes the page, otherwise we are just setting up the new page
if(!setupPage)
return;
//Setup Next Page
if(page == Page.EVENT_PREF){//Setup pref values XXXXX Check this
TextView foodLabel = findViewById(R.id.foodPrefText);
String foodValue = Settings.GetPref(Settings.foodPrefKey, getApplicationContext());
if(foodLabel != null)
foodLabel.setText(foodValue);
TextView sbbLabel = findViewById(R.id.sbbPrefText);
String sbbValue = Settings.GetPref(Settings.sbbPrefKey, getApplicationContext());
if(sbbLabel != null)
sbbLabel.setText(sbbValue);
} }
} }
...@@ -204,10 +250,23 @@ public class IntroActivity extends AppCompatActivity { ...@@ -204,10 +250,23 @@ public class IntroActivity extends AppCompatActivity {
if(hasLoggedIn) if(hasLoggedIn)
hasLoggedIn = Settings.HasToken(getApplicationContext());//check if user is only logged in by mail, or if we have no user profile to edit hasLoggedIn = Settings.HasToken(getApplicationContext());//check if user is only logged in by mail, or if we have no user profile to edit
if(resultCode == RESULT_OK) if(resultCode == RESULT_OK) {
viewPager.setCurrentItem(hasLoggedIn ? 2 : 3); if(hasLoggedIn){
SetPage(Page.EDIT_PROFILE, true);
Snackbar.make(viewPager, "Fetching Profile", 1000).show();
Requests.FetchUserData(getApplicationContext(), viewPager, new Requests.OnDataReceivedCallback() {
@Override
public void OnDataReceived() {
SetProfileUI();
}
});
}
else
SetPage(Page.EVENT_PREF, true);
}
else else
viewPager.setCurrentItem(1);//means the user canceled using back, show the previous page SetPage(Page.APP_INFO, true);//means the user canceled using back, show the previous page
} }
} }
...@@ -218,7 +277,7 @@ public class IntroActivity extends AppCompatActivity { ...@@ -218,7 +277,7 @@ public class IntroActivity extends AppCompatActivity {
} }
private void StartMainAcivity() { private void StartMainAcivity() {
Settings.SetIntroDone(true, this); Settings.SetBoolPref(Settings.introDoneKey, true, this);
startActivity(new Intent(this, MainActivity.class)); startActivity(new Intent(this, MainActivity.class));
finish(); finish();
} }
...@@ -250,6 +309,90 @@ public class IntroActivity extends AppCompatActivity { ...@@ -250,6 +309,90 @@ public class IntroActivity extends AppCompatActivity {
//region---Profile--- //region---Profile---
/**
* Use this to fill in the profile text fields once the userInfo has been received
*/
private void SetProfileUI(){
if(rfidField == null)
rfidField = findViewById(R.id.rfidField);
rfidField.setText(UserInfo.current.rfid);
}
/**
* Use this to update the profile info and submit to the server
*/
private void UpdateProfile(){
String newRfid = rfidField.getText().toString();
if(newRfid.isEmpty() || newRfid.equalsIgnoreCase(UserInfo.current.rfid))
return;
UserInfo.current.rfid = newRfid;
Requests.PatchUserData(getApplicationContext());
}
//endregion
//region---Prefs---
public void EditFoodPrefs(View view){
SetPage(Page.PREF_DETAIL, true);
final PrefDetailView.OnButtonIndexClicked onClick = new PrefDetailView.OnButtonIndexClicked() {
@Override
public void OnClick(int enumIndex) {//use length-1 when other is used
if(enumIndex < 0)
return;
SetPage(Page.EVENT_PREF, false);
//Set the event pref
String value = getResources().getStringArray(R.array.pref_food_list_values)[enumIndex];
Settings.SetPref(Settings.foodPrefKey, value, getApplicationContext());
if(enumIndex == getResources().getStringArray(R.array.pref_food_list_values).length -1 && findViewById(R.id.otherField) != null) {
Settings.SetPref(Settings.specialFoodPrefKey, ((EditText)findViewById(R.id.otherField)).getText().toString(), getApplicationContext());
}
TextView label = findViewById(R.id.foodPrefText);
if(label != null)
label.setText(getResources().getStringArray(R.array.pref_food_list_values)[enumIndex]);
btnNext.setOnClickListener(onNextClick);
}
};
PrefDetailView.InitialiseList(this, R.string.pref_food_title, onClick, getResources().getStringArray(R.array.pref_food_list_values), true);
btnNext.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onClick.OnClick(getResources().getStringArray(R.array.pref_food_list_values).length -1);
}
});
}
public void EditSBBPrefs(View view){
SetPage(Page.PREF_DETAIL, true);
final PrefDetailView.OnButtonIndexClicked onClick = new PrefDetailView.OnButtonIndexClicked() {
@Override
public void OnClick(int enumIndex) {//use length-1 when other is used
if(enumIndex < 0)
return;
SetPage(Page.EVENT_PREF, false);
String value = getResources().getStringArray(R.array.pref_sbb_list_values)[enumIndex];
Settings.SetPref(Settings.sbbPrefKey, value, getApplicationContext());
TextView label = findViewById(R.id.sbbPrefText);
if(label != null)
label.setText(getResources().getStringArray(R.array.pref_sbb_list_values)[enumIndex]);
btnNext.setOnClickListener(onNextClick);
}
};
PrefDetailView.InitialiseList(this, R.string.pref_sbb_title, onClick, getResources().getStringArray(R.array.pref_sbb_list_values), false);
btnNext.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onClick.OnClick(getResources().getStringArray(R.array.pref_sbb_list_values).length -1);
}
});
}
//endregion //endregion
//endregion //endregion
} }
...@@ -46,7 +46,7 @@ public class LoginActivity extends AppCompatActivity { ...@@ -46,7 +46,7 @@ public class LoginActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
isIntroLogin = !Settings.GetIntroDone(this); isIntroLogin = !Settings.GetBoolPref(Settings.introDoneKey, getApplicationContext());
//Set for the keyboard to resize the window so the snackbars appear just above the keyboard //Set for the keyboard to resize the window so the snackbars appear just above the keyboard
prevLayoutParams = getWindow().getAttributes().softInputMode; prevLayoutParams = getWindow().getAttributes().softInputMode;
......
...@@ -101,9 +101,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On ...@@ -101,9 +101,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
PersistentStorage.LoadJobs(getApplicationContext()); PersistentStorage.LoadJobs(getApplicationContext());
InitialisePageView(); InitialisePageView();
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. //fetch the user info if we are logged in, there exists a token from the previous session, should be cached.
if(!PersistentStorage.LoadUserInfo(getApplicationContext()) || UserInfo.current._id.isEmpty() && !Settings.IsEmailOnlyLogin(getApplicationContext())) { if(!PersistentStorage.LoadUserInfo(getApplicationContext()) || UserInfo.current._id.isEmpty() && !Settings.IsEmailOnlyLogin(getApplicationContext())) {
Requests.FetchUserData(getApplicationContext(), drawerNavigation, new Requests.OnDataReceivedCallback() { Requests.FetchUserData(getApplicationContext(), drawerNavigation, new Requests.OnDataReceivedCallback() {
@Override @Override
......
...@@ -32,10 +32,27 @@ import java.util.Map; ...@@ -32,10 +32,27 @@ import java.util.Map;
import ch.amiv.android_app.events.Events; import ch.amiv.android_app.events.Events;
import ch.amiv.android_app.jobs.Jobs; import ch.amiv.android_app.jobs.Jobs;
/**
* A static class to do backround http requests to the amiv api. Access these requests anywhere
* Most requests have a callback, so you can execute code when the request has returned or failed
* See API Docs to see what requests can be done: https://api.amiv.ethz.ch/docs or via github site https://github.com/amiv-eth/amivapi
*
* It is advised to test requests first with Postman or similar.
* To see the output of the requests setup Postman as a proxy server, and then set your computer's IP as the proxy in the phones wi-fi setting for the current connection
*
* Generally, call a FetchX function from anywhere, which will create and format the request then use SendRequest to submit the formatted request
* When creating your own, have a look at the override functions available for the StringRequest. Note the difference between getHeaders and getParams
*
* To add auth with a token, from settings see one of the functions as an example, eg. FetchEventSignups
*
* To load images, don't use a request directly, use a networkImageView, which will handle everything for you including caching
*
* Libary used for network stuff: volley, note: we use our own modified version of the libary as a git submodule
*/
public final class Requests { public final class Requests {
private static RequestQueue requestQueue; private static RequestQueue requestQueue;
private static ImageLoader imageLoader; private static ImageLoader imageLoader;
private static final int MAX_CACHED_IMAGES = 50; private static final int MAX_CACHED_IMAGES = 75;
public static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); public static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
...@@ -389,6 +406,64 @@ public final class Requests { ...@@ -389,6 +406,64 @@ public final class Requests {
boolean hasSent = Requests.SendRequest(request, context); boolean hasSent = Requests.SendRequest(request, context);
} }
/**
* Will update the user data in the amiv api
* XXX buffer and send request when internet is regained, and retry there is an error
*/
public static void PatchUserData(final Context context){
if(!Settings.HasToken(context) || !CheckConnection(context))
return;
//Do patch request to /user/{userId}
String url = Settings.API_URL + "users/" + UserInfo.current._id;
StringRequest request = new StringRequest(Request.Method.PATCH, 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", "status Code: " + response.statusCode);
}
else
Log.e("request", "Request returned null response. fetch user data");
return super.parseNetworkResponse(response);
}
@Override
protected VolleyError parseNetworkError(final VolleyError volleyError) { //see comments at parseNetworkResponse()
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 user data");
return super.parseNetworkError(volleyError);
}
@Override
public Map<String, String> getHeaders() {
Map<String,String> headers = new HashMap<String, String>();
// Add basic auth with token
String credentials = Settings.GetToken(context) + ":";
String auth = "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
headers.put("Authorization", auth);
headers.put("if-match", UserInfo.current._etag);
return headers;
}
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String,String> params = new HashMap<String, String>();
params.put("rfid", UserInfo.current.rfid);
return params;
}
};
boolean hasSent = Requests.SendRequest(request, context);
}
/** /**
* Will send a Delete request to delete the current session and token with it. * Will send a Delete request to delete the current session and token with it.
* XXX When there is no internet or an error the request is not completed. the session persists on the server. need to rerun request on next time we have a connection * XXX When there is no internet or an error the request is not completed. the session persists on the server. need to rerun request on next time we have a connection
......
...@@ -9,6 +9,8 @@ import android.util.Log; ...@@ -9,6 +9,8 @@ import android.util.Log;
import java.util.Locale; import java.util.Locale;
import javax.xml.validation.Validator;
import ch.amiv.android_app.R; import ch.amiv.android_app.R;
/** /**
...@@ -17,7 +19,6 @@ import ch.amiv.android_app.R; ...@@ -17,7 +19,6 @@ import ch.amiv.android_app.R;
* Access the settings with the according get function from the static instance. * Access the settings with the according get function from the static instance.
*/ */
public class Settings { public class Settings {
public static Settings instance;
//public static final String API_URL = "http://192.168.1.105:5000/"; //public static final String API_URL = "http://192.168.1.105:5000/";
public static final String API_URL = "https://api-dev.amiv.ethz.ch/"; public static final String API_URL = "https://api-dev.amiv.ethz.ch/";
...@@ -25,47 +26,21 @@ public class Settings { ...@@ -25,47 +26,21 @@ public class Settings {
//Whether to show hidden events, where the adverts should not have started yet, should later be set by user access group //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; 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 //---PREF KEYS---- 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 SharedPreferences sharedPrefs;
public 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";
private static final boolean defaultTheme = false; //false for light
private static final String apiTokenKey = "ch.amiv.android_app.apitoken";
private static final String introDoneKey = "ch.amiv.android_app.introdone";
private static Vibrator vibrator; //Keys are always two values, (Key for shared prefs, Default value)
public static final class VibrateTime { //For boolean values true=1, false=0 (or anything else)
public static final int SHORT = 50; public static final String[] apiUrlPrefKey = {"ch.amiv.android_app.serverurl", "https://api-dev.amiv.ethz.ch"};
public static final int NORMAL = 100; public static final String[] apiTokenKey = {"ch.amiv.android_app.apitoken", ""};
public static final int LONG = 250; public static final String[] introDoneKey = {"ch.amiv.android_app.introdone", "0"};
}
public static void Vibrate(int millisecs, Context context){
if(vibrator == null)