Commit 6067d8fe authored by Roger Barton's avatar Roger Barton
Browse files

Merge branch 'notifications' into additional_fields

parents 3e9464dd c887d453
Pipeline #8602 passed with stages
in 8 minutes and 44 seconds
This is a sample of the "additional_fields" variable found in the json for an event
{
"$schema":"http://json-schema.org/draft-04/schema#",
"additionalProperties":false,
......
For POST request to signups, to send additional fields as part of the signup use this format
"additional_fields":
{
"SBB_Abo":"Halbtax",
"Food":"vegi"
}
\ No newline at end of file
......@@ -131,5 +131,17 @@
android:parentActivityName=".core.MainActivity"
android:screenOrientation="portrait" /> -->
<receiver android:name=".core.AlarmReceiver"/>
<receiver android:name=".core.AlarmBootReceiver"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"></action>
</intent-filter>
</receiver>
</application>
</manifest>
\ No newline at end of file
......@@ -54,7 +54,7 @@ public class SettingsActivity extends AppCompatActivity {
Settings.SetBoolPref(Settings.checkin_autoUpdate, mAutoRefreshCheck.isChecked(), getApplicationContext());
Settings.SetFloatPref(Settings.checkin_refreshRate, MathUtils.clamp(Float.parseFloat(mRefreshFreqField.getText().toString()), 3f, Float.POSITIVE_INFINITY), getApplicationContext());
ReturnToMainActivity();
finish();
}
/**
......@@ -63,10 +63,4 @@ public class SettingsActivity extends AppCompatActivity {
public static int GetRefreshRateMillis(Context context) {
return (int)(1000 * Settings.GetFloatPref(Settings.checkin_refreshRate, context));
}
private void ReturnToMainActivity ()
{
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
}
}
package ch.amiv.android_app.core;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class AlarmBootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
Notifications.set_Alarm(context);
}
}
}
package ch.amiv.android_app.core;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class AlarmReceiver extends BroadcastReceiver {
int notification_id=0;
@Override
public void onReceive(Context context, Intent intent) {
//Notifications.notify(context,"Alarm","Receiver called",R.drawable.ic_amiv_logo_icon);
Request.FetchEventListChanges(context, new Request.OnDataReceivedCallback() {
@Override
public void OnDataReceived() {
}
}, new Request.OnDataReceivedCallback() {
@Override
public void OnDataReceived() {
}
}, "2018-05-06T10:00:00Z",false); // TODO change date here to use last checked date
// use Settings.GetPref(Settings.last_change_check_dateKey,context)
// TODO test if notification needed
// TODO enter event notifier here
notification_id++;
}
}
......@@ -2,6 +2,7 @@ package ch.amiv.android_app.core;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
......@@ -17,6 +18,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import ch.amiv.android_app.R;
import ch.amiv.android_app.events.AdditField;
import ch.amiv.android_app.util.ui.NonSwipeableViewPager;
import ch.amiv.android_app.util.ui.EnumViewGenerator;
import ch.amiv.android_app.util.Util;
......@@ -286,6 +288,10 @@ public class IntroActivity extends AppCompatActivity {
private void StartMainAcivity() {
Settings.SetBoolPref(Settings.introDoneKey, true, this);
// TODO check if notifications are enabled in the settings
Notifications.set_Alarm(this);
startActivity(new Intent(this, MainActivity.class));
finish();
}
......@@ -367,7 +373,7 @@ public class IntroActivity extends AppCompatActivity {
}
};
EnumViewGenerator.InitialiseEnumList(this, R.string.pref_food_title, onClick, getResources().getStringArray(R.array.pref_food_list_values), true);
EnumViewGenerator.InitialiseEnumList(this, onClick, AdditField.Defaults.food, true);
btnNext.setOnClickListener(new View.OnClickListener() {
@Override
......@@ -398,7 +404,7 @@ public class IntroActivity extends AppCompatActivity {
}
};
EnumViewGenerator.InitialiseEnumList(this, R.string.pref_sbb_title, onClick, getResources().getStringArray(R.array.pref_sbb_list_values), false);
EnumViewGenerator.InitialiseEnumList(this, onClick, AdditField.Defaults.sbbAbo, false);
btnNext.setOnClickListener(new View.OnClickListener() {
@Override
......
......@@ -55,6 +55,7 @@ public class ListFragment extends Fragment {
public void OnDataReceived() {
Request.FetchEventSignups(MainActivity.instance, onSignupsUpdatedCallback, null, "");
RefreshList(true);
}
};
......
package ch.amiv.android_app.core;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.BottomNavigationView;
......@@ -27,9 +31,9 @@ import ch.amiv.android_app.R;
import ch.amiv.android_app.events.EventDetailActivity;
import ch.amiv.android_app.events.Events;
import ch.amiv.android_app.jobs.JobDetailActivity;
import ch.amiv.android_app.util.PersistentStorage;
import ch.amiv.android_app.util.Util;
/**
* This is the first screen. features: drawer, pageview with bottom navigation bar and within each page a list view.
*/
......@@ -37,6 +41,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
public static MainActivity instance;
//region - ====Variables====
private NavigationView drawerNav;
private TextView drawer_title;
......@@ -81,6 +86,27 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
instance = this;
setContentView(R.layout.core_main);
// Create the NotificationChannel to run notifications on API 26+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "1";
String description = "Notifications";
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel("1", name, importance);
channel.setDescription(description);
// Register the channel in the system
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
/*
//Use this to set a custom taskDescription in the app overview, ie when switching apps. Can set the icon, label and color of the bar
Resources r = getResources();
ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription(r.getString(R.string.app_name),
BitmapFactory.decodeResource(getResources(), R.drawable.ic_amiv_logo_icon_white),
r.getColor(R.color.white));
this.setTaskDescription(taskDescription);*/
Toolbar toolbar = Util.SetupToolbar(this, false);
DrawerLayout drawer = findViewById(R.id.drawer_layout);
......@@ -96,12 +122,13 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
bottomNavigation = findViewById(R.id.bottomNav);
bottomNavigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
PersistentStorage.LoadEvents(getApplicationContext());
PersistentStorage.LoadJobs(getApplicationContext());
Settings.LoadEvents(getApplicationContext());
Settings.LoadJobs(getApplicationContext());
InitialisePageView();
//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(!Settings.LoadUserInfo(getApplicationContext()) || UserInfo.current._id.isEmpty() && !Settings.IsEmailOnlyLogin(getApplicationContext())) {
Request.FetchUserData(getApplicationContext(), drawerNav, new Request.OnDataReceivedCallback() {
@Override
public void OnDataReceived() {
......
package ch.amiv.android_app.core;
import android.app.AlarmManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Calendar;
import ch.amiv.android_app.R;
import ch.amiv.android_app.events.EventDetailActivity;
import ch.amiv.android_app.events.Events;
public final class Notifications {
public static AlarmManager alarm;
public static Intent intent;
public static PendingIntent pendingIntent;
/**
*
* @param context
* @return sets daily alarm to XX:XX
*/
static void set_Alarm (Context context){
// set alarm time in calendar
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 8);
calendar.set(Calendar.MINUTE, 23);
calendar.set(Calendar.SECOND, 0);
// pending intent to activate activity when notification is clicked
intent = new Intent(context, AlarmReceiver.class);
pendingIntent = PendingIntent.getBroadcast(
context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT); // updates intent if it already exists
// set alarm manager
alarm = (AlarmManager) context
.getSystemService(context.ALARM_SERVICE);
alarm.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
AlarmManager.INTERVAL_FIFTEEN_MINUTES, pendingIntent);
}
/**
*
* @param context
* @param title title of notification
* @param text text of notification
* @param icon icon of notification
* @return immediately generates notification on screen
*/
public static void notify (Context context, String title, String text, int icon){
NotificationManager notificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder mNotifyBuilder = new NotificationCompat.Builder(
context,"1")
.setSmallIcon(icon)
.setContentTitle(title)
.setContentText(text)
.setAutoCancel(true);
notificationManager.notify(0, mNotifyBuilder.build());
}
/**
*
* @param context
* @param title title of notification
* @param text text of notification
* @param icon icon
* @param pendingIntent pending intent to activate if notification is clicked
* @return generates notification on screen which starts the pending activity onClick
*/
public static void notify_pending (Context context, String title, String text, int icon, PendingIntent pendingIntent){
NotificationManager notificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder mNotifyBuilder = new NotificationCompat.Builder(
context,"1")
.setSmallIcon(icon)
.setContentTitle(title)
.setContentText(text)
.setColor(context.getResources().getColor(R.color.primary)).setColorized(true)
.setAutoCancel(true)
.setContentIntent(pendingIntent);
notificationManager.notify(0, mNotifyBuilder.build());
}
/**
*
* @param context
* @param json JSON with all new events
* @return event notificiation with appropriate onClick event
*/
public static void event_notifier (Context context, JSONObject json){
// TODO catch when projections are used
try{
JSONArray items = json.getJSONArray("_items");
if(items.length()!=0) {
// for one new event -> notify and onClick show details
if(items.length()==1) {
// get event from JSONObject and add it to events list
JSONObject event = items.getJSONObject(0);
Events.AddEvent(event, context);
// refetch event list to add new event
String event_id = (String) event.get("_id");
Request.FetchEventList(context, null, null, event_id);
// generate pending intent to get EventDetails onClick
NotificationManager notificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
Intent notificationIntent = new Intent(context, EventDetailActivity.class);
notificationIntent.putExtra(EventDetailActivity.LauncherExtras.EVENT_ID, event_id);
notificationIntent.putExtra(EventDetailActivity.LauncherExtras.LOAD_EVENTS, true);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// generate notification
String title = (String) event.get("title_de");
Notifications.notify_pending(context, "New Event added", title, R.drawable.ic_amiv_logo_icon, pendingIntent);
}
// multiple new events -> show all titles and onClick show events main page
else{
// get event id's from new events
String [] event_id = new String[items.length()];
for(int i = 0; i<items.length();i++){
JSONObject event = items.getJSONObject(i);
Events.AddEvent(event, context); // TODO efficiency !
event_id[i]= (String) event.get("_id");
}
// add new events to list
// TODO how -> new function?
Request.FetchEventList(context, null, null,event_id[0]); // TODO not null !
// onClick start main activity
NotificationManager notificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
Intent notificationIntent = new Intent(context, MainActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// build string to display all event titles
StringBuilder titles_list = new StringBuilder();
titles_list.append ((String)items.getJSONObject(0).get("title_de"));
for(int j = 1; j<items.length();j++){
titles_list.append(", ");
titles_list.append((String)items.getJSONObject(j).get("title_de"));
}
Notifications.notify_pending(context, "Many new Events added", titles_list.toString(), R.drawable.ic_amiv_logo_icon, pendingIntent);
}
}
// sets last change check to current time
String dat = Request.dateFormat.format(Calendar.getInstance().getTime());
Settings.SetPref(Settings.last_change_check_dateKey,dat,context);
}catch(JSONException ex){
// TODO
}
}
}
......@@ -80,6 +80,94 @@ public final class Request {
return true;
}
/**
* Will fetch the list of events from the server, note does not require an access token.
* @param errorCallback Use this to know when an error occurred to stop loading animations etc
* @param last_update_time last changes check time
*/
public static void FetchEventListChanges(final Context context, final OnDataReceivedCallback callback, final OnDataReceivedCallback errorCallback, @NonNull final String last_update_time, boolean projection)
{
if(!CheckConnection(context)) {
RunCallback(errorCallback);
return;
}
String url;
if (projection) {
url = Settings.API_URL + "events?" + "projection={\"_id\":1}" + "&where={\"_created\":{\"$gt\":\"" + last_update_time + "\"}, \"show_website\": true}";
}
else{
url = Settings.API_URL + "events?" + "where={\"_created\":{\"$gt\":\"" + last_update_time + "\"}, \"show_website\": true}";
}
Log.e("request", "url: " + url);
StringRequest request = new StringRequest(com.android.volley.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 events status Code: " + response.statusCode);
try {
final JSONObject json = new JSONObject(new String(response.data));
//Update events on main thread
Runnable runnable = new Runnable() {
@Override
public void run() {
// TODO alarmreceiver
Notifications.event_notifier(context,json);
if(callback != null)
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 events");
}
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 events");
RunCallback(errorCallback);
return super.parseNetworkError(volleyError);
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
if(Settings.HasToken(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(!Request.SendRequest(request, context))
RunCallback(errorCallback);
}
/**
* Will fetch the list of events from the server, note does not require an access token.
* @param errorCallback Use this to know when an error occurred to stop loading animations etc
......
......@@ -7,9 +7,20 @@ import android.content.res.Resources;
import android.os.Vibrator;
import android.util.Pair;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.json.JSONObject;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Locale;
import ch.amiv.android_app.R;
import ch.amiv.android_app.events.EventInfo;
import ch.amiv.android_app.events.Events;
import ch.amiv.android_app.jobs.JobInfo;
import ch.amiv.android_app.jobs.Jobs;
/**
* This class is used to save settings so they can be restored in another session later.
......@@ -35,6 +46,11 @@ public class Settings {
public static final String[] specialFoodPrefKey = {"core.special_food_pref", ""};
public static final String[] sbbPrefKey = {"core.sbb_abo", ""};
//Storing of larger data
public static final String[] userInfoKey = {"core.user_info", ""};
public static final String[] eventInfoKey = {"events.event_infos", ""};
public static final String[] jobInfoKey = {"jobs.job_infos", ""};
//region---Check-in----
public static final String[] recentEventPin = {"checkin.recent_event_pin", ""};
public static final String[] checkin_url = {"checkin.serverurl", "https://checkin.amiv.ethz.ch"};
......@@ -43,6 +59,9 @@ public class Settings {
public static final Pair<String, Float> checkin_refreshRate = new Pair<>("checkin.refreshfrequency", 20f);
//endregion
//last changes check
public static final String[] last_change_check_dateKey = {"core.notifications_date", "1979-02-19T10:00:00Z"};
//region ---SharedPrefs---
/**
* Will check that the shared prefs instance is set so we can edit/retrieve values
......@@ -52,6 +71,11 @@ public class Settings {
sharedPrefs = context.getSharedPreferences(SHARED_PREFS_KEY, Context.MODE_PRIVATE);
}
public static boolean HasKey(String[] key, Context context){
CheckInitSharedPrefs(context);
return sharedPrefs.contains(key[0]);
}
/**
* Will store the value in sharedpreferences to be restored in another session