Skip to content

Commit

Permalink
Merge branch 'development' of github.com:ZeusWPI/hydra-android into d…
Browse files Browse the repository at this point in the history
…evelopment
  • Loading branch information
niknetniko committed Nov 2, 2017
2 parents 80c0e3a + 19a543b commit 11053e9
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@
import be.ugent.zeus.hydra.data.models.minerva.Agenda;
import be.ugent.zeus.hydra.data.models.minerva.AgendaItem;
import be.ugent.zeus.hydra.data.models.minerva.Course;
import java8.lang.Iterables;
import java8.util.Objects;
import java8.util.function.Function;
import java8.util.stream.Collectors;
import java8.util.stream.StreamSupport;
import org.threeten.bp.ZonedDateTime;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;

/**
* Attempts to filter duplicates from a list of calendar items.
Expand All @@ -23,11 +21,18 @@
*/
public class AgendaDuplicateDetector implements Function<Agenda, Agenda> {

/**
* These are edit modes we do not want to show to the user.
*/
private static final Set<String> HIDDEN_TYPES = new HashSet<>(Collections.singletonList("set_invisible"));

@Override
public Agenda apply(Agenda agenda) {

List<AgendaItem> agendaItems = agenda.getItems();

Iterables.removeIf(agendaItems, item -> HIDDEN_TYPES.contains(item.getLastEditType()));

// We first categorize the items per course.
Map<Course, List<AgendaItem>> mapped = StreamSupport.stream(agendaItems)
.collect(Collectors.groupingBy(AgendaItem::getCourse));
Expand Down Expand Up @@ -100,7 +105,6 @@ private List<AgendaItem> filterDuplicates(Course course, List<AgendaItem> items)
}

// If there are none left, we add them all, since we don't know which one you want.
assert noMoreOasis.isEmpty();
finalItems.addAll(mergeLocations(endList));
}
}
Expand All @@ -109,33 +113,31 @@ private List<AgendaItem> filterDuplicates(Course course, List<AgendaItem> items)
}

private List<AgendaItem> mergeLocations(List<AgendaItem> items) {
// Check if they are all the same
boolean isSame = true;
AgendaItem last = items.get(0);
for (AgendaItem item : items.subList(1, items.size())) {
// We check the title and description. We already know other things, such as the
// dates are the same.
if (!TextUtils.equals(item.getTitle(), last.getTitle()) || !TextUtils.equals(item.getContent(), item.getContent())) {
isSame = false;
break;
}
}

if (isSame) {
// Merge them into one, with an adjusted location. We merge into the first one.
// TODO: better joining
List<AgendaItem> finalItems = new ArrayList<>();

// We currently consider two events the same if their titles are the same. Group the events by title.
Map<String, List<AgendaItem>> perTitle = StreamSupport.stream(items)
.collect(Collectors.groupingBy(AgendaItem::getTitle));

String[] locations = StreamSupport.stream(items)
for (List<AgendaItem> item : perTitle.values()) {
if (item.size() == 1) {
finalItems.add(item.get(0));
continue;
}
// Get the first one.
AgendaItem first = items.get(0);
// Merge the locations.
String[] locations = StreamSupport.stream(item)
.map(AgendaItem::getLocation)
.filter(Objects::nonNull)
.distinct()
.toArray(String[]::new);
last.setMerged(true);
last.setLocation(TextUtils.join("\n", locations));
return Collections.singletonList(last);
} else {
// Else we just add them all, since they differ in ways we don't support yet.
return items;
first.setLocation(TextUtils.join("\n", locations));
first.setMerged(true);
finalItems.add(first);
}

return finalItems;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import android.provider.CalendarContract;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;

import be.ugent.zeus.hydra.BuildConfig;
Expand All @@ -31,6 +32,7 @@
import be.ugent.zeus.hydra.repository.requests.Result;
import be.ugent.zeus.hydra.ui.minerva.CalendarPermissionActivity;
import be.ugent.zeus.hydra.ui.preferences.MinervaFragment;
import be.ugent.zeus.hydra.utils.StringUtils;
import java8.util.Maps;
import java8.util.function.Functions;
import java8.util.stream.Collectors;
Expand All @@ -51,13 +53,12 @@
public class CalendarSync {

private static final String TAG = "CalendarSync";
private static final String FIRST_SYNC_BUILT_IN_CALENDAR = "once_first_calendar";
private static long NO_CALENDAR = -1;
private final AgendaDao calendarDao;
private final CourseDao courseDao;
private final Context context;

private static final String FIRST_SYNC_BUILT_IN_CALENDAR = "once_first_calendar";

public CalendarSync(AgendaDao calendarDao, CourseDao courseDao, Context context) {
this.calendarDao = calendarDao;
this.courseDao = courseDao;
Expand Down Expand Up @@ -245,6 +246,11 @@ private void synchronizeCalendar(Account account, boolean isInitialSync, Synchro
}
}

// Sometimes our database loses events, or the user selects another option causing
// the device calendar to contain things that are no longer in our calendar. We cannot know the id of those,
// so we get all ids, remove the items we still know about and remove the rest.
Set<Long> allDeviceIds = getAllIdsFromDeviceCalendar(account, resolver);

// Update Calendar items, as they might have changed.
for (AgendaItem updatedItem : diff.getUpdated()) {
long itemCalendarId = updatedItem.getCalendarId();
Expand All @@ -256,9 +262,16 @@ private void synchronizeCalendar(Account account, boolean isInitialSync, Synchro
} else { // Update the item.
Uri itemUri = ContentUris.withAppendedId(uri, itemCalendarId);
resolver.update(itemUri, values, null, null);
allDeviceIds.remove(itemCalendarId);
}
}

// Remove the events we lost from the calendar.
for (Long id : allDeviceIds) {
Uri itemUri = ContentUris.withAppendedId(uri, id);
resolver.delete(itemUri, null, null);
}

// Add new items to the calendar.
for (AgendaItem newItem : diff.getNew()) {
ContentValues values = toCalendarValues(calendarId, newItem);
Expand Down Expand Up @@ -339,13 +352,39 @@ private int deleteCalendarFor(Account account, ContentResolver resolver) {

String[] selArgs = new String[]{account.name, MinervaConfig.ACCOUNT_TYPE};

int rows = resolver.delete(
return resolver.delete(
CalendarContract.Calendars.CONTENT_URI,
selection,
selArgs
);
}

private Set<Long> getAllIdsFromDeviceCalendar(Account account, ContentResolver resolver) {

String[] projection = {CalendarContract.Events._ID};

return rows;
String selection =
CalendarContract.Calendars.ACCOUNT_NAME +
" = ? AND " +
CalendarContract.Calendars.ACCOUNT_TYPE +
" = ? ";

String[] selArgs = new String[]{account.name, MinervaConfig.ACCOUNT_TYPE};

Cursor result = resolver.query(CalendarContract.Calendars.CONTENT_URI, projection, selection, selArgs, null);
if (result == null) {
return new HashSet<>();
}

try {
Set<Long> resultSet = new HashSet<>();
while (result.moveToNext()) {
resultSet.add(result.getLong(result.getColumnIndexOrThrow(CalendarContract.Events._ID)));
}
return resultSet;
} finally {
result.close();
}
}

/**
Expand Down Expand Up @@ -398,8 +437,8 @@ private int getCalendarColour() {
private ContentValues toCalendarValues(long calendarId, AgendaItem item) {
ContentValues contentValues = new ContentValues();
contentValues.put(CalendarContract.Events.CALENDAR_ID, calendarId);
contentValues.put(CalendarContract.Events.TITLE, item.getTitle());
contentValues.put(CalendarContract.Events.DESCRIPTION, item.getContent());
contentValues.put(CalendarContract.Events.TITLE, getTitleFor(item));
contentValues.put(CalendarContract.Events.DESCRIPTION, getDescriptionFor(item));
contentValues.put(CalendarContract.Events.DTSTART, item.getStartDate().toInstant().toEpochMilli());
contentValues.put(CalendarContract.Events.DTEND, item.getEndDate().toInstant().toEpochMilli());
// Convert Java 8 TimeZone to old TimeZone
Expand All @@ -412,4 +451,53 @@ private ContentValues toCalendarValues(long calendarId, AgendaItem item) {
contentValues.put(CalendarContract.Events.CUSTOM_APP_URI, item.getUri());
return contentValues;
}

/**
* Get the title for an event in the calendar, intended for the device calendar. This method will consider the user
* preferences in regards to prefixing with the course's name and using acronyms or not.
*
* @param item The item to get the title for.
*
* @return The title.
*/
private String getTitleFor(AgendaItem item) {
String title;
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
if (preferences.getBoolean(MinervaFragment.PREF_PREFIX_EVENT_TITLES, false)
&& !TextUtils.equals(item.getTitle(), item.getCourse().getTitle())) {
String courseTitle;
if (preferences.getBoolean(MinervaFragment.PREF_PREFIX_EVENT_ACRONYM, true)) {
courseTitle = StringUtils.generateAcronymFor(item.getCourse().getTitle());
} else {
courseTitle = item.getCourse().getTitle();
}
title = context.getString(R.string.minerva_calendar_device_event_title, courseTitle, item.getTitle());
} else {
title = item.getTitle();
}
return title;
}

/**
* Get the description for an event in the calendar, intended for the device calendar. This method will append the
* course's name to the description, if the user preference for prefixing event titles was turned on.
*
* @param item The item to get the description from.
*
* @return The description.
*/
private String getDescriptionFor(AgendaItem item) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
if (preferences.getBoolean(MinervaFragment.PREF_PREFIX_EVENT_TITLES, false)) {
String original = item.getContent();
String description = context.getString(R.string.minerva_calendar_device_description, item.getCourse().getTitle());
if (TextUtils.isEmpty(original)) {
return description;
} else {
return original + "\n\n" + description;
}
} else {
return item.getContent();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ public class MinervaFragment extends PreferenceFragment {
public static final String PREF_ANNOUNCEMENT_NOTIFICATION_EMAIL = "pref_minerva_announcement_notification_email";
public static final String PREF_USE_MOBILE_URL = "pref_minerva_use_mobile_url";


public static final String PREF_DETECT_DUPLICATES = "pref_minerva_detect_duplicates";

public static final String PREF_PREFIX_EVENT_TITLES = "pref_minerva_prefix_event_titles";
public static final String PREF_PREFIX_EVENT_ACRONYM = "pref_minerva_prefix_event_acronym";

//In seconds
public static final String PREF_DEFAULT_SYNC_FREQUENCY = "86400";
public static final boolean PREF_DEFAULT_ANNOUNCEMENT_NOTIFICATION_EMAIL = false;
Expand All @@ -41,6 +43,12 @@ public class MinervaFragment extends PreferenceFragment {
private boolean oldDetectDuplicates;
private boolean newDetectDuplicates;

private boolean oldPrefixEventTitles;
private boolean newPrefixEventTitles;

private boolean oldPrefixAcronyms;
private boolean newPrefixAcronyms;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Expand All @@ -63,9 +71,17 @@ public void onCreate(Bundle savedInstanceState) {
oldDetectDuplicates = preferences.getBoolean(PREF_DETECT_DUPLICATES, false);
newDetectDuplicates = oldDetectDuplicates;

oldPrefixEventTitles = preferences.getBoolean(PREF_PREFIX_EVENT_TITLES, false);
newPrefixEventTitles = oldPrefixEventTitles;

oldPrefixAcronyms = preferences.getBoolean(PREF_PREFIX_EVENT_ACRONYM, true);
newPrefixAcronyms = oldPrefixAcronyms;

Preference intervalPreference = findPreference(PREF_SYNC_FREQUENCY);
Preference detectPreference = findPreference(PREF_DETECT_DUPLICATES);
Preference prefixTitles = findPreference(PREF_PREFIX_EVENT_TITLES);
Preference prefixAbbreviations = findPreference(PREF_PREFIX_EVENT_ACRONYM);
prefixAbbreviations.setEnabled(oldPrefixEventTitles);

intervalPreference.setOnPreferenceChangeListener((preference, newValue) -> {
newInterval = Integer.parseInt((String) newValue);
Expand All @@ -77,6 +93,17 @@ public void onCreate(Bundle savedInstanceState) {
return true;
});

prefixTitles.setOnPreferenceChangeListener((preference, newValue) -> {
newPrefixEventTitles = (boolean) newValue;
prefixAbbreviations.setEnabled(newPrefixEventTitles);
return true;
});

prefixAbbreviations.setOnPreferenceChangeListener((preference, newValue) -> {
newPrefixAcronyms = (boolean) newValue;
return true;
});

if(!AccountUtils.hasAccount(getAppContext())) {
intervalPreference.setEnabled(false);
}
Expand All @@ -89,7 +116,7 @@ public void onPause() {
if (hasAccount && oldInterval != newInterval) {
SyncUtils.changeSyncFrequency(getAppContext(), MinervaConfig.SYNC_AUTHORITY, newInterval);
}
if (hasAccount && oldDetectDuplicates != newDetectDuplicates) {
if (hasAccount && (oldDetectDuplicates != newDetectDuplicates || oldPrefixEventTitles != newPrefixEventTitles || oldPrefixAcronyms != newPrefixAcronyms)) {
Bundle bundle = new Bundle();
bundle.putBoolean(MinervaAdapter.SYNC_ANNOUNCEMENTS, false);
SyncUtils.requestSync(AccountUtils.getAccount(getAppContext()), MinervaConfig.SYNC_AUTHORITY, bundle);
Expand Down
30 changes: 30 additions & 0 deletions app/src/main/java/be/ugent/zeus/hydra/utils/StringUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,34 @@ public static String convertStreamToString(@NonNull InputStream is) {
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}

/**
* Generate an acronym for a string. This will split the string on whitespace, take the first letter of every word,
* capitalize that first letter and concat the result. If a word does not contain any letters, it is fully added.
*
* For example, {@code Algoritmen en datastructuren III} will become {@code AEDIII}.
*
* TODO: can we make this less dumb, and ignore words as 'en', 'of', etc?
*
* @param name The string to acronymize.
*
* @return The acronym.
*/
public static String generateAcronymFor(String name) {
StringBuilder result = new StringBuilder();
for (String word : name.split("\\s")) {
int i = 0;
char c;
do {
c = word.charAt(i++);
} while (!Character.isLetter(c) && i < word.length());
if (Character.isLetter(c)) {
result.append(Character.toUpperCase(c));
} else {
// There are no letters in this part, so just add it completely.
result.append(word);
}
}
return result.toString();
}
}
8 changes: 7 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">

<!-- General -->
<string name="app_name">Hydra</string>
Expand Down Expand Up @@ -204,6 +204,12 @@
<string name="events_no_location">Zonder locatie</string>
<string name="title_license">Licenties</string>
<string name="agenda_subtitle">%1$s • %2$s</string>
<string name="minerva_calendar_device_event_title">
<xliff:g id="course_name" example="Machinaal leren">%1$s</xliff:g>: <xliff:g id="event_title" example="Inleiding en afspraken">%2$s</xliff:g>
</string>
<string name="minerva_calendar_device_description">
Les van het vak <xliff:g id="course_name" example="Machinaal leren">%1$s</xliff:g>
</string>
<plurals name="home_feed_announcement_title">
<item quantity="one">%d aankondiging</item>
<item quantity="other">%d aankondigingen</item>
Expand Down
Loading

0 comments on commit 11053e9

Please sign in to comment.