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

Merge branch 'additional_fields' into notifications

parents d507d6c2 3e9464dd
Pipeline #8499 passed with stages
in 2 minutes and 45 seconds
"$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"]
{
"$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"]
}
\ No newline at end of file
{
"id": "http://json-schema.org/draft-04/schema#",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
},
"positiveInteger": {
"type": "integer",
"minimum": 0
},
"positiveIntegerDefault0": {
"allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
},
"simpleTypes": {
"enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"uniqueItems": true
}
},
"type": "object",
"properties": {
"id": {
"type": "string"
},
"$schema": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": {},
"multipleOf": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "boolean",
"default": false
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "boolean",
"default": false
},
"maxLength": { "$ref": "#/definitions/positiveInteger" },
"minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": {
"anyOf": [
{ "type": "boolean" },
{ "$ref": "#" }
],
"default": {}
},
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": {}
},
"maxItems": { "$ref": "#/definitions/positiveInteger" },
"minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"maxProperties": { "$ref": "#/definitions/positiveInteger" },
"minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
"required": { "$ref": "#/definitions/stringArray" },
"additionalProperties": {
"anyOf": [
{ "type": "boolean" },
{ "$ref": "#" }
],
"default": {}
},
"definitions": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/stringArray" }
]
}
},
"enum": {
"type": "array",
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{ "$ref": "#/definitions/simpleTypes" },
{
"type": "array",
"items": { "$ref": "#/definitions/simpleTypes" },
"minItems": 1,
"uniqueItems": true
}
]
},
"format": { "type": "string" },
"allOf": { "$ref": "#/definitions/schemaArray" },
"anyOf": { "$ref": "#/definitions/schemaArray" },
"oneOf": { "$ref": "#/definitions/schemaArray" },
"not": { "$ref": "#" }
},
"dependencies": {
"exclusiveMaximum": [ "maximum" ],
"exclusiveMinimum": [ "minimum" ]
},
"default": {}
}
......@@ -125,11 +125,11 @@
</activity>
<!-- Demo -->
<activity
<!--<activity
android:name=".demo.MainActivity"
android:configChanges="layoutDirection|locale"
android:parentActivityName=".core.MainActivity"
android:screenOrientation="portrait" />
android:screenOrientation="portrait" /> -->
......
......@@ -271,16 +271,16 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
public void StartEventDetailActivity(int eventGroup, int eventIndex)
{
Intent intent = new Intent(this, EventDetailActivity.class);
intent.putExtra("eventGroup", eventGroup);
intent.putExtra("eventIndex", eventIndex);
intent.putExtra(EventDetailActivity.LauncherExtras.EVENT_GROUP, eventGroup);
intent.putExtra(EventDetailActivity.LauncherExtras.EVENT_INDEX, eventIndex);
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);
intent.putExtra(JobDetailActivity.LauncherExtras.JOB_GROUP, jobGroup);
intent.putExtra(JobDetailActivity.LauncherExtras.JOB_INDEX, jobIndex);
startActivityForResult(intent, 0);
}
......
......@@ -22,7 +22,7 @@ public class MainActivity extends AppCompatActivity {
String firstname = UserInfo.current.firstname;
//To access events use
EventInfo eventInfo = Events.sortedEvents.get(0).get(1);//sorted into groups by date
EventInfo eventInfo = Events.sortedEventInfos.get(0).get(1);//sorted into groups by date
eventInfo = Events.eventInfos.get(0);//unsorted list
//To Send a request to the api or elsewhere see core.Request class for examples. Basic structure is as below
......
package ch.amiv.android_app.events;
import android.graphics.drawable.AdaptiveIconDrawable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* A data class for storing a single additional Field for an event. See the SampleEventAdditFields file.
* An instance of this class represents one variable.
* Use an array of this type to generate a form for the event
*/
public class AdditField {
//The types of fields we can have, according to the json schema, see SampleJsonSchemaAdditFields
public static final class FieldType {
public static final int ARRAY = 0;
public static final int BOOLEAN = 1;
public static final int INTEGER = 2;
public static final int NULL = 3;
public static final int NUMBER = 4;
public static final int OBJECT = 5;
public static final int STRING = 6;
}
public int type; //Use the FieldType constants, the currentValue should then be of that type
public String name;
public boolean required;
public String[] possibleValues;
public String currentValue;
/**
*
* @param additional_fields The 'additional_fields' JsonObject from the event json
* @return A parsed array of AdditFields. Empty array if we failed to parse
*/
public static AdditField[] ParseFromJson(JSONObject additional_fields){
List<AdditField> fields;
JSONObject properties;
List<String> requiredFields = null;
//Get the properties json
try {
properties = new JSONObject(additional_fields.getString("properties"));
fields = new ArrayList<>(properties.length());
} catch (JSONException e) {
e.printStackTrace();
return new AdditField[0];//return empty array if we cannot parse
}
//parse which fields are required
try {
JSONArray requiredFieldsJson = new JSONArray(additional_fields.getString("required"));
requiredFields = Arrays.asList(ParseStringArray(requiredFieldsJson));
} catch (JSONException e) { }
//If we have the data, parse it
Iterator<String> iter = properties.keys(); //iterate over all items in the properties array, which each represent one AdditField
while (iter.hasNext()) {
String key = iter.next();
try {
JSONObject o = properties.getJSONObject(key);
AdditField f = new AdditField();
f.name = key;
f.type = ParseFieldType(o.getString("type"));
//Get possible values from enum
if(o.has("enum"))
f.possibleValues = ParseStringArray(new JSONArray(o.getString("enum")));
//Check if this field is in the required list
if(requiredFields != null && requiredFields.contains(f.name))
f.required = true;
fields.add(f);
}
catch (JSONException e){
e.printStackTrace();
}
}
Object[] objects = fields.toArray();
return Arrays.copyOf(objects, objects.length, AdditField[].class);
}
/**
* Will convert a type string from the json to a FieldType
* @param type Type string from the AdditionalFields Json
* @return FieldType
*/
private static int ParseFieldType(String type){
if(type == null) return FieldType.STRING;
switch (type){
case "array":
return FieldType.ARRAY;
case "boolean":
return FieldType.BOOLEAN;
case "integer":
return FieldType.INTEGER;
case "null":
return FieldType.NULL;
case "number":
return FieldType.NUMBER;
case "object":
return FieldType.OBJECT;
case "string":
return FieldType.STRING;
default:
return FieldType.STRING;
}
}
/**
* Will parse a json String array to a java string array
* @param array
*/
private static String[] ParseStringArray(JSONArray array){
String[] result = new String[array.length()];
for (int i = 0; i < result.length; ++i){
try {
result[i] = array.getString(i);
} catch (JSONException e) {
e.printStackTrace();
}
}
return result;
}
}
......@@ -43,29 +43,32 @@ import ch.amiv.android_app.core.LoginActivity;
import ch.amiv.android_app.core.Request;
import ch.amiv.android_app.core.Settings;
import ch.amiv.android_app.core.UserInfo;
import ch.amiv.android_app.util.PersistentStorage;
import ch.amiv.android_app.util.Util;
/**
* The activity/screen used for showing a selected event in detail.
* This mainly displays stored info about the event, eg description and also fetches more such as images. Also handles registering for and event and the possible outcomes
*
* To launch this activity, you need to provide an event to view. You need to provide this as an intent extra (use intent.putExtra()):
* - Provide eventGroup == -1, eventGroup == index in Events.eventInfos (unsorted list)
* - Provide eventGroup == group in Events.sortedEventInfos, eventGroup == index in Events.sortedEventInfos (sorted list)
* - Provide eventId == any valid event id
*
* If the event is not found, the activity finishes and returns to the calling activity.
*/
public class EventDetailActivity extends AppCompatActivity {
private int eventGroup = 0;
private int eventIndex = 0;
private EventInfo event(){ //Used to easily access the activities event
if(!hasEvent())
return null;
return Events.sortedEvents.get(eventGroup).get(eventIndex);
/**
* A constant class to easily set extras for launching the EventDetailActivity
*/
public static final class LauncherExtras {
public static final String EVENT_GROUP = "eventGroup";
public static final String EVENT_INDEX = "eventIndex";
public static final String EVENT_ID = "eventId";
public static final String LOAD_EVENTS = "loadEvents";
}
private boolean hasEvent(){
if(eventGroup >= Events.sortedEvents.size() || eventIndex >= Events.sortedEvents.get(eventGroup).size()){
Log.e("events", "EventDetailActivity given invalid event indexes, (group, index) = (" + eventGroup + ", " + eventIndex + "), with sortedEvents size of 1st dim: " + Events.sortedEvents.size());
return false;
}
return true;
}
private EventInfo event;
private ImageView posterImage;
private ImageView posterMask;
......@@ -89,7 +92,7 @@ public class EventDetailActivity extends AppCompatActivity {
@Override
public void OnDataReceived() {
SetUIDirty(true, false);
Request.FetchEventSignups(getApplicationContext(), onSignupsUpdatedCallback, cancelRefreshCallback, event()._id);
Request.FetchEventSignups(getApplicationContext(), onSignupsUpdatedCallback, cancelRefreshCallback, event._id);
LoadEventImage(true);
}
};
......@@ -104,9 +107,9 @@ public class EventDetailActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
GetIntentData();
super.onCreate(savedInstanceState);
setContentView(R.layout.events_detail);
GetIntentData();
InitUI();
}
......@@ -145,7 +148,11 @@ public class EventDetailActivity extends AppCompatActivity {
@Override
public void onBackPressed() {
//return to the calling activity with the set response in the intent, such as whether the login state has changed
setResult(RESULT_OK, responseIntent);
ReturnToCallingActivity(true);
}
private void ReturnToCallingActivity (boolean success){
setResult(success ? RESULT_OK : RESULT_CANCELED, responseIntent);
finish();
}
......@@ -153,13 +160,33 @@ public class EventDetailActivity extends AppCompatActivity {
* This will retrieve the eventIndexes to display, is only set when we originate from the MainActivity, where the int is added to the intent.
*/
private void GetIntentData (){
if(eventGroup == 0 && eventIndex == 0) {
Intent intent = getIntent();
if(intent.hasExtra("eventGroup") && intent.hasExtra("eventIndex")) {
eventGroup = intent.getIntExtra("eventGroup", 0);
eventIndex = intent.getIntExtra("eventIndex", 0);
}
Intent intent = getIntent();
if(intent.getBooleanExtra(LauncherExtras.LOAD_EVENTS, false))
PersistentStorage.LoadEvents(getApplicationContext());
if(intent.hasExtra(LauncherExtras.EVENT_GROUP) && intent.hasExtra(LauncherExtras.EVENT_INDEX))
{
int eventGroup = intent.getIntExtra(LauncherExtras.EVENT_GROUP, 0);
int eventIndex = intent.getIntExtra(LauncherExtras.EVENT_INDEX, 0);
if(eventGroup == -1)
event = Events.eventInfos.get(eventIndex);
else
event = Events.sortedEventInfos.get(eventGroup).get(eventIndex);
if (event == null)
Log.e("events", "invalid event index selected during InitUI(), (groupIndex, eventIndex): (" + eventGroup + "," + eventIndex + "), total event size" + Events.eventInfos.size() + ". Ensure that you are not clearing/overwriting the events list while viewing an event. Returning to calling activity...");
}
else if(intent.hasExtra(LauncherExtras.EVENT_ID))
{
event = Events.GetById(intent.getStringExtra(LauncherExtras.EVENT_ID));
if(event == null)
Log.e("events", "No event found from eventId=" + intent.getStringExtra(LauncherExtras.EVENT_ID) + " in intent, have you used intent.putStringExtra. Returning to calling activity...");
}
if(event == null)
ReturnToCallingActivity(false);
}
/**
......@@ -169,11 +196,7 @@ public class EventDetailActivity extends AppCompatActivity {
Util.SetupToolbar(this, true);
//Check that we have been given an event that exists else return to the calling activity
if(!hasEvent()) {
Log.e("events", "invalid event index selected during InitUI(), (groupIndex, eventIndex): (" + eventGroup + "," + eventIndex + "), total event size" + Events.eventInfos.size() + ". Ensure that you are not clearing/overwriting the events list while viewing an event.");
onBackPressed();
return;
}
if(event == null) return;
//Link up variables with UI elements from the layout xml
scrollView = findViewById(R.id.scrollView);
......@@ -230,16 +253,16 @@ public class EventDetailActivity extends AppCompatActivity {
}
private void OnSwipeRefreshed(){
if(!hasEvent())
return;
Request.FetchEventList(getApplicationContext(), onEventsListUpdatedCallback, cancelRefreshCallback, event()._id);
if(event == null) return;
Request.FetchEventList(getApplicationContext(), onEventsListUpdatedCallback, cancelRefreshCallback, event._id);
}
public void SetUIDirty(boolean isRefreshing, boolean signupUpdated)
{
if(!signupUpdated) {
((TextView) findViewById(R.id.eventTitle)).setText(event().GetTitle(getResources()));
((TextView) findViewById(R.id.eventDetail)).setText(event().GetDescription(getResources()));
((TextView) findViewById(R.id.eventTitle)).setText(event.GetTitle(getResources()));
((TextView) findViewById(R.id.eventDetail)).setText(event.GetDescription(getResources()));
LoadEventImage(isRefreshing);
AddRegisterInfos();
}
......@@ -253,7 +276,7 @@ public class EventDetailActivity extends AppCompatActivity {
private void LoadEventImage (boolean isRefreshing)
{
//Image loading and masking. the posterMask is a small arrow image but we use the layout margin to add some transparent 'padding' to the top of the scrollview
if(event().poster_url.isEmpty() || !Request.CheckConnection(getApplicationContext()))
if(event.poster_url.isEmpty() || !Request.CheckConnection(getApplicationContext()))
{
//Hide the image and mask if there is no poster linked with the event or we have no internet
if(!isRefreshing) {
......@@ -283,7 +306,7 @@ public class EventDetailActivity extends AppCompatActivity {
}
//Send a request for the image, note we can also use a NetworkImageView, but have less control then
ImageRequest posterRequest = new ImageRequest(event().GetPosterUrl(),
ImageRequest posterRequest = new ImageRequest(event.GetPosterUrl(),
new Response.Listener<Bitmap>() {
@Override
public void onResponse(final Bitmap bitmap) {
......@@ -332,7 +355,7 @@ public class EventDetailActivity extends AppCompatActivity {
LinearLayout linear = findViewById(R.id.register_details_list);
linear.removeAllViews();
ArrayList<String[]> infos = event().GetInfos(getResources());
ArrayList<String[]> infos = event.GetInfos(getResources());
LayoutInflater inflater = LayoutInflater.from(getApplicationContext());
for (int i = 0; i < infos.size(); i++) {
//Create a view from the xml and then add it as a child of the listview
......@@ -355,15 +378,13 @@ public class EventDetailActivity extends AppCompatActivity {
}
//Redirect to the login page first
if(!Settings.HasToken(getApplicationContext()) && !event().allow_email_signup){
if(!Settings.HasToken(getApplicationContext()) && !event.allow_email_signup){
Intent intent = new Intent(this, LoginActivity.class);
intent.putExtra("cause", "register_event");
startActivityForResult(intent, 0);
return;
}
final int registerEventGroup = eventGroup; //declare final so it does not change
final int registerEventIndex = eventIndex;
String url = Settings.API_URL + "eventsignups";
StringRequest request = new StringRequest(com.android.volley.Request.Method.POST, url,null, null)
......@@ -376,7 +397,7 @@ public class EventDetailActivity extends AppCompatActivity {
try {
Log.e("request", new String(response.data));
JSONObject json = new JSONObject(new String(response.data));
event().AddSignup(json); //Register signup
event.AddSignup(json); //Register signup
//Fetch event signup object again for this event id
Request.FetchEventSignups(getApplicationContext(), new Request.OnDataReceivedCallback() {
......@@ -384,12 +405,12 @@ public class EventDetailActivity extends AppCompatActivity {
public void OnDataReceived() {
UpdateRegisterButton();
}