diff --git a/app/build.gradle b/app/build.gradle index 8dc646a02..67012df0c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -104,8 +104,8 @@ android { compileOptions { coreLibraryDesugaringEnabled true - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } buildTypes { @@ -120,7 +120,7 @@ android { debug { // Disable crashlytics in debug builds if necessary. ext.enableCrashlytics = Boolean.parseBoolean(props.getProperty("hydra.debug.reporting")) - testCoverageEnabled true +// testCoverageEnabled true } } @@ -199,6 +199,9 @@ dependencies { implementation 'dev.chrisbanes.insetter:insetter:0.6.1' implementation 'com.github.niqdev:ipcam-view:2.4.0' + annotationProcessor 'io.soabase.record-builder:record-builder-processor:37' + compileOnly 'io.soabase.record-builder:record-builder-core:37' + // Dependencies for the Play Store version. storeImplementation 'com.google.android.gms:play-services-maps:18.2.0' storeImplementation 'com.google.firebase:firebase-analytics:21.4.0' @@ -230,9 +233,10 @@ dependencies { testImplementation 'nl.jqno.equalsverifier:equalsverifier:3.15.2' testImplementation 'com.shazam:shazamcrest:0.11' testImplementation 'org.skyscreamer:jsonassert:1.5.1' - testImplementation 'org.jeasy:easy-random-core:5.0.0' + testImplementation 'org.jeasy:easy-random-core:6.0.0-SNAPSHOT' testImplementation 'org.apache.commons:commons-lang3:3.13.0' testImplementation 'commons-validator:commons-validator:1.7' + testImplementation 'com.google.guava:guava:32.1.3-jre' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ddbb5717f..05c72e8b2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -118,7 +118,7 @@ diff --git a/app/src/main/java/be/ugent/zeus/hydra/MainActivity.java b/app/src/main/java/be/ugent/zeus/hydra/MainActivity.java index a8df6b213..706c996df 100644 --- a/app/src/main/java/be/ugent/zeus/hydra/MainActivity.java +++ b/app/src/main/java/be/ugent/zeus/hydra/MainActivity.java @@ -74,13 +74,13 @@ *

* The logic for handling the navigation drawer is not immediately obvious to those who do not work with it regularly. * Proceed with caution. - * + *

*

Navigation

*

* One of the main responsibilities of this activity is managing navigation between the drawer, the fragments and the * fragments between each other. The navigation is built in two main components: navigation forward and navigating * backwards. Each component itself is not that difficult. Together the provide an intuitive navigation. - * + *

*

Forward navigation

*

* When the user navigates to a new fragment in this activity, the back stack (of the {@link #getSupportFragmentManager()} @@ -112,7 +112,7 @@ *

* The third and last scenario is the easiest: nothing should be done when the activity is first started or recreated. * The fragments should not be added to the back stack. - * + *

*

Backwards navigation

*

* The logic above makes the backwards navigation quite simple, and can be summarized as: @@ -149,7 +149,7 @@ * After this method call, the fragment can behave as if the arguments were directly set on the fragment itself. *

* This function will only be called when creating a fragment, not when popping from the back stack. - * + *

*

Common views and removal

*

* The activity provides some common views: @@ -172,7 +172,7 @@ *

* The reason fragments cannot fully rely on the default lifecycle methods, such as {@link Fragment#onStop()}, is that * the fragment is not always removed immediately by the activity when it is hidden (this as to do with performance). - * + *

*

Arguments

*

* The activity has one public argument: which child fragment to load. The preferred way of using it is @@ -683,24 +683,14 @@ public interface OnBackPressed { private static final class TutorialEndEvent implements Event { @Nullable @Override - public String getEventName() { + public String eventName() { return Reporting.getEvents().tutorialComplete(); } } /** - * Groups an update for the navigation drawer. - */ - private static class DrawerUpdate { - @NavigationSource - final int navigationSource; - final Fragment fragment; - final MenuItem menuItem; - - private DrawerUpdate(int navigationSource, Fragment fragment, MenuItem menuItem) { - this.navigationSource = navigationSource; - this.fragment = fragment; - this.menuItem = menuItem; + * Groups an update for the navigation drawer. + */ + private record DrawerUpdate(@NavigationSource int navigationSource, Fragment fragment, MenuItem menuItem) { } - } } diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/Association.java b/app/src/main/java/be/ugent/zeus/hydra/association/Association.java index 4e6d5dbf2..43e00aaeb 100644 --- a/app/src/main/java/be/ugent/zeus/hydra/association/Association.java +++ b/app/src/main/java/be/ugent/zeus/hydra/association/Association.java @@ -26,8 +26,8 @@ import android.os.Parcelable; import androidx.annotation.Nullable; +import java.util.Collections; import java.util.List; -import java.util.Objects; /** * Represents an association registered with the DSA. @@ -35,40 +35,38 @@ * @author feliciaan * @author Niko Strijbol */ -public final class Association implements Parcelable { - - private String abbreviation; - private String name; - private List path; - @Nullable - private String description; - private String email; - @Nullable - private String logo; - @Nullable - private String website; - - public Association() { - // Moshi uses this! - } - - /** @noinspection ProtectedMemberInFinalClass*/ - protected Association(Parcel in) { - abbreviation = in.readString(); - name = in.readString(); - path = in.createStringArrayList(); - description = in.readString(); - email = in.readString(); - logo = in.readString(); - website = in.readString(); +public record Association( + String abbreviation, + String name, + List path, + @Nullable String description, + @Nullable String email, + @Nullable String logo, + @Nullable String website +) implements Parcelable { + + private Association(Parcel in) { + this( + in.readString(), + in.readString(), + in.createStringArrayList(), + in.readString(), + in.readString(), + in.readString(), + in.readString() + ); } public static Association unknown(String name) { - Association association = new Association(); - association.abbreviation = "unknown"; - association.name = name; - association.description = "Onbekende vereniging"; - return association; + return new Association( + "unknown", + name, + Collections.emptyList(), + "Onbekende vereniging", + null, + null, + null + ); } @Override @@ -98,43 +96,4 @@ public Association[] newArray(int size) { return new Association[size]; } }; - - @Nullable - public String getDescription() { - return description; - } - - @Nullable - public String getWebsite() { - return website; - } - - /** - * @return A name for this association. If a full name is available, that is returned. If not, the display name is. - */ - public String getName() { - return name; - } - - public String getAbbreviation() { - return abbreviation; - } - - @Nullable - public String getImageLink() { - return logo; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Association that = (Association) o; - return Objects.equals(abbreviation, that.abbreviation); - } - - @Override - public int hashCode() { - return Objects.hash(abbreviation); - } } diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/event/EventPage.java b/app/src/main/java/be/ugent/zeus/hydra/association/AssociationList.java similarity index 63% rename from app/src/main/java/be/ugent/zeus/hydra/association/event/EventPage.java rename to app/src/main/java/be/ugent/zeus/hydra/association/AssociationList.java index 5d5619a4a..b7088fc75 100644 --- a/app/src/main/java/be/ugent/zeus/hydra/association/event/EventPage.java +++ b/app/src/main/java/be/ugent/zeus/hydra/association/AssociationList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 The Hydra authors + * Copyright (c) 2023 Niko Strijbol * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,33 +20,28 @@ * SOFTWARE. */ -package be.ugent.zeus.hydra.association.event; +package be.ugent.zeus.hydra.association; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Collections; import java.util.List; import java.util.Objects; /** - * @author Niko Strijbol + * Top-level response object for the DSA API. + *

+ * This is basically an object with an association list inside it. + * While the list won't be null most of the time, we do not control this API, + * so we assume is can be null to have a more robust app. */ -public final class EventPage { - - /** @noinspection unused*/ - private List entries; - - public List getEntries() { - return entries; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - EventPage eventPage = (EventPage) o; - return Objects.equals(entries, eventPage.entries); - } - +public record AssociationList( + @Nullable List associations +) { + @NonNull @Override - public int hashCode() { - return Objects.hash(entries); + public List associations() { + return Objects.requireNonNullElse(associations, Collections.emptyList()); } } diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/common/AssociationMap.java b/app/src/main/java/be/ugent/zeus/hydra/association/AssociationMap.java similarity index 55% rename from app/src/main/java/be/ugent/zeus/hydra/association/common/AssociationMap.java rename to app/src/main/java/be/ugent/zeus/hydra/association/AssociationMap.java index 4fbd1383c..acaa015bc 100644 --- a/app/src/main/java/be/ugent/zeus/hydra/association/common/AssociationMap.java +++ b/app/src/main/java/be/ugent/zeus/hydra/association/AssociationMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 The Hydra authors + * Copyright (c) 2023 Niko Strijbol * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,24 +20,41 @@ * SOFTWARE. */ -package be.ugent.zeus.hydra.association.common; +package be.ugent.zeus.hydra.association; import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; -import be.ugent.zeus.hydra.association.Association; - /** - * Represents a mapping of association abbreviation to their object. + * A class representing a map of associations. * - * @author Niko Strijbol + *

+ * The AssociationMap class provides methods to access associations based on their abbreviation, + * check if an association was requested to be shown, and combine the requested associations with + * the association list. + *

*/ -public interface AssociationMap { +public class AssociationMap { + + private final Map associationMap; + private final Set associationRequested; + + public AssociationMap() { + this(Collections.emptyList(), Collections.emptySet()); + } + + public AssociationMap(@NonNull List list, @NonNull Set requestedAssociations) { + this.associationMap = new HashMap<>(); + for (var association : list) { + associationMap.put(association.abbreviation(), association); + } + this.associationRequested = requestedAssociations; + } /** * Get the association linked to the specified mapping. @@ -49,29 +66,35 @@ public interface AssociationMap { * @return The association. */ @NonNull - Association get(@Nullable String abbreviation); + public Association get(@Nullable String abbreviation) { + return associationMap.computeIfAbsent(abbreviation, Association::unknown); + } /** * @return A stream of all known associations. */ - Stream associations(); + @NonNull + public Stream associations() { + return associationMap.values().stream(); + } /** - * Check if the associations was requested to be shown by the request that + * Check if the association was requested to be shown by the request that * produced this association map. - * + * * @param abbreviation The abbreviation. - * * @return True if requested to be shown, false otherwise. */ - boolean isRequested(@NonNull String abbreviation); + public boolean isRequested(@NonNull String abbreviation) { + return associationRequested.contains(abbreviation); + } /** * Combine the {@link #isRequested(String)} data with the association list. */ - default List> getSelectedAssociations() { + public List> requestedAssociations() { return this.associations() - .map(a -> Pair.create(a, this.isRequested(a.getAbbreviation()))) + .map(a -> Pair.create(a, this.isRequested(a.abbreviation()))) .collect(Collectors.toList()); } } diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/AssociationRequest.java b/app/src/main/java/be/ugent/zeus/hydra/association/AssociationRequest.java new file mode 100644 index 000000000..96594a81c --- /dev/null +++ b/app/src/main/java/be/ugent/zeus/hydra/association/AssociationRequest.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2022 The Hydra authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package be.ugent.zeus.hydra.association; + +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Objects; + +import androidx.annotation.VisibleForTesting; + +import be.ugent.zeus.hydra.common.network.Endpoints; +import be.ugent.zeus.hydra.common.network.JsonOkHttpRequest; +import be.ugent.zeus.hydra.common.request.Request; + +/** + * Builder to create various requests to get associations from the DSA. + * + * @author Niko Strijbol + */ +public class AssociationRequest extends JsonOkHttpRequest { + + private static final String FILENAME = "verenigingen"; + + @VisibleForTesting + AssociationRequest(@NonNull Context context) { + super(context, AssociationList.class); + } + + @NonNull + @Override + protected String apiUrl() { + return Endpoints.DSA_V4 + FILENAME; + } + + @Override + public Duration cacheDuration() { + return ChronoUnit.WEEKS.getDuration().multipliedBy(4); + } + + public record EventsAndAssociations( + AssociationMap associations, + List events + ) { + } + + public record EventItemsAndAssociations( + AssociationMap associations, + List events + ) { + } + + /** + * Returns a request object that retrieves a list of associations. + * + * @param context The context. + * @return A request object that retrieves a list of associations. + */ + public static Request> associationListRequest(@NonNull Context context) { + return new AssociationRequest(context) + .map(AssociationList::associations); + } + + /** + * Creates a filtered event item request. + *

+ * This contains all events that satisfy the filter, with an association map. + * + * @param context The context of the application. + * @param filter The event filter to apply. + * @return A request object containing a pair of an association map and a list of event items. + */ + public static Request filteredEventItemRequest(@NonNull Context context, @NonNull EventFilter filter) { + return filteredEventRequest(context, filter) + .map(results -> { + var eventItems = EventItem.fromEvents(results.events); + return new EventItemsAndAssociations(results.associations, eventItems); + }); + } + + /** + * Creates a filtered events request. + *

+ * This contains all events that satisfy the filter, with an association map. + * + * @param context The context of the application. + * @param optionalFilter The event filter to apply. + * @return A request object containing a pair of an association map and a list of events. + */ + public static Request filteredEventRequest(@NonNull Context context, @Nullable EventFilter optionalFilter) { + var filter = Objects.requireNonNullElse(optionalFilter, new EventFilter()); + return associationListRequest(context) + .andThen(associations -> { + var eventRequestFilter = filter.toRequestFilter(context, associations); + var requestedAssociations = eventRequestFilter.getRequestedAssociations(); + var associationMap = new AssociationMap(associations, requestedAssociations); + return EventRequest.eventRequest(context, eventRequestFilter) + .map(events -> new EventsAndAssociations(associationMap, events)); + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/common/AssociationVisibilityStorage.java b/app/src/main/java/be/ugent/zeus/hydra/association/AssociationVisibilityStorage.java similarity index 92% rename from app/src/main/java/be/ugent/zeus/hydra/association/common/AssociationVisibilityStorage.java rename to app/src/main/java/be/ugent/zeus/hydra/association/AssociationVisibilityStorage.java index e33a91f29..d7afc755b 100644 --- a/app/src/main/java/be/ugent/zeus/hydra/association/common/AssociationVisibilityStorage.java +++ b/app/src/main/java/be/ugent/zeus/hydra/association/AssociationVisibilityStorage.java @@ -20,7 +20,7 @@ * SOFTWARE. */ -package be.ugent.zeus.hydra.association.common; +package be.ugent.zeus.hydra.association; import android.content.Context; import android.content.SharedPreferences; @@ -33,8 +33,6 @@ import java.util.*; import java.util.stream.Collectors; -import be.ugent.zeus.hydra.association.Association; - /** * Class that manages storing which associations the user wants to see * and which they don't. @@ -73,13 +71,13 @@ public static Set calculateWhitelist(Context context, List // Start with all associations. Set whitelist = new HashSet<>(); for (Association association : associations) { - whitelist.add(association.getAbbreviation()); + whitelist.add(association.abbreviation()); } // Get the existing blacklist, either from storage or from the selected one. Set blacklist; if (newWhitelist == null) { - blacklist = getBlacklist(context); + blacklist = blacklist(context); // Remove any obsolete associations. Set obsolete = new HashSet<>(); @@ -94,7 +92,7 @@ public static Set calculateWhitelist(Context context, List } else { blacklist = newWhitelist.stream() .filter(p -> !p.second) - .map(p -> p.first.getAbbreviation()) + .map(p -> p.first.abbreviation()) .collect(Collectors.toCollection(HashSet::new)); } @@ -110,7 +108,7 @@ public static Set calculateWhitelist(Context context, List * Get the saved blacklist. */ @NonNull - public static Set getBlacklist(@NonNull Context context) { + public static Set blacklist(@NonNull Context context) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); return new HashSet<>(preferences.getStringSet(PREF_BLACKLIST, new HashSet<>())); } @@ -123,7 +121,7 @@ public static Set getBlacklist(@NonNull Context context) { * @param abbreviation The abbreviation of the association you want to whitelist. */ public static void blacklist(@NonNull Context context, @NonNull String abbreviation) { - Set existing = getBlacklist(context); + Set existing = blacklist(context); existing.add(abbreviation); saveBlacklist(context, existing); } @@ -136,7 +134,7 @@ public static void blacklist(@NonNull Context context, @NonNull String abbreviat * @param abbreviation The abbreviation of the association you want to whitelist. */ public static void whitelist(@NonNull Context context, @NonNull String abbreviation) { - Set existing = getBlacklist(context); + Set existing = blacklist(context); existing.remove(abbreviation); saveBlacklist(context, existing); } diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/event/Event.java b/app/src/main/java/be/ugent/zeus/hydra/association/Event.java similarity index 59% rename from app/src/main/java/be/ugent/zeus/hydra/association/event/Event.java rename to app/src/main/java/be/ugent/zeus/hydra/association/Event.java index 113a228f7..d8f4709b0 100644 --- a/app/src/main/java/be/ugent/zeus/hydra/association/event/Event.java +++ b/app/src/main/java/be/ugent/zeus/hydra/association/Event.java @@ -20,7 +20,7 @@ * SOFTWARE. */ -package be.ugent.zeus.hydra.association.event; +package be.ugent.zeus.hydra.association; import android.os.Parcel; import android.os.Parcelable; @@ -28,12 +28,11 @@ import java.time.LocalDateTime; import java.time.OffsetDateTime; -import java.util.Objects; -import be.ugent.zeus.hydra.association.Association; import be.ugent.zeus.hydra.common.converter.DateTypeConverters; import be.ugent.zeus.hydra.common.utils.DateUtils; import com.squareup.moshi.Json; +import io.soabase.recordbuilder.core.RecordBuilder; /** * Event from an {@link Association}. @@ -41,51 +40,47 @@ * @author Niko Strijbol * @author feliciaan */ -public final class Event implements Parcelable, Comparable { - - private long id; - private String title; - @Json(name = "start_time") - private OffsetDateTime start; - @Json(name = "end_time") - private OffsetDateTime end; - private String location; - private String address; - private String description; - @Json(name = "infolink") - private String url; - private String association; - private boolean advertise; - - public Event() { - // Moshi uses this! - } - - protected Event(Parcel in) { - id = in.readLong(); - title = in.readString(); - location = in.readString(); - address = in.readString(); - description = in.readString(); - url = in.readString(); - association = in.readString(); - advertise = in.readInt() == 1; - start = end = DateTypeConverters.toOffsetDateTime(in.readString()); - end = DateTypeConverters.toOffsetDateTime(in.readString()); +@RecordBuilder +public record Event( + long id, + String title, + @Json(name = "start_time") OffsetDateTime start, + @Nullable @Json(name = "end_time") OffsetDateTime end, + @Nullable String location, + @Nullable String address, + @Nullable String description, + @Nullable @Json(name = "infolink") String url, + String association, + boolean advertise +) implements Parcelable, Comparable, EventBuilder.With { + + private Event(Parcel in) { + this( + in.readLong(), + in.readString(), + DateTypeConverters.toOffsetDateTime(in.readString()), + DateTypeConverters.toOffsetDateTime(in.readString()), + in.readString(), + in.readString(), + in.readString(), + in.readString(), + in.readString(), + in.readInt() == 1 + ); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(id); dest.writeString(title); + dest.writeString(DateTypeConverters.fromOffsetDateTime(start)); + dest.writeString(DateTypeConverters.fromOffsetDateTime(end)); dest.writeString(location); dest.writeString(address); dest.writeString(description); dest.writeString(url); dest.writeString(association); dest.writeInt(advertise ? 1 : 0); - dest.writeString(DateTypeConverters.fromOffsetDateTime(start)); - dest.writeString(DateTypeConverters.fromOffsetDateTime(end)); } @Override @@ -93,7 +88,7 @@ public int describeContents() { return 0; } - public static final Creator CREATOR = new Creator() { + public static final Creator CREATOR = new Creator<>() { @Override public Event createFromParcel(Parcel in) { return new Event(in); @@ -113,8 +108,8 @@ public Event[] newArray(int size) { * * @return The converted start date. */ - public LocalDateTime getLocalStart() { - return DateUtils.toLocalDateTime(getStart()); + public LocalDateTime localStart() { + return DateUtils.toLocalDateTime(start()); } /** @@ -126,56 +121,23 @@ public LocalDateTime getLocalStart() { * @return The converted end date. */ @Nullable - public LocalDateTime getLocalEnd() { - if (getEnd() == null) { + public LocalDateTime localEnd() { + if (end == null) { return null; } - return DateUtils.toLocalDateTime(getEnd()); - } - - public String getTitle() { - return title; - } - - public OffsetDateTime getStart() { - return start; - } - - @Nullable - public OffsetDateTime getEnd() { - return end; - } - - public String getLocation() { - return location; - } - - public String getAddress() { - return address; + return DateUtils.toLocalDateTime(end); } public boolean hasPreciseLocation() { - return getAddress() != null; + return address != null; } public boolean hasLocation() { - return getLocation() != null && !getLocation().trim().isEmpty(); - } - - public String getDescription() { - return description; - } - - public String getUrl() { - return url; + return location != null && !location.trim().isEmpty(); } public boolean hasUrl() { - return getUrl() != null && !getUrl().trim().isEmpty(); - } - - public String getAssociation() { - return association; + return url != null && !url.trim().isEmpty(); } /** @@ -187,8 +149,12 @@ public String getAssociation() { * * @return The identifier. */ - public String getIdentifier() { - return title + start.toString() + end.toString() + location + url + association; + public String identifier() { + String end = ""; + if (this.end != null) { + end = this.end.toString(); + } + return title + start.toString() + end + location + url + association; } /** @@ -198,17 +164,4 @@ public String getIdentifier() { public int compareTo(Event o) { return start.compareTo(o.start); } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Event event = (Event) o; - return id == event.id; - } - - @Override - public int hashCode() { - return Objects.hash(id); - } } diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/event/EventDetailsActivity.java b/app/src/main/java/be/ugent/zeus/hydra/association/EventDetailsActivity.java similarity index 79% rename from app/src/main/java/be/ugent/zeus/hydra/association/event/EventDetailsActivity.java rename to app/src/main/java/be/ugent/zeus/hydra/association/EventDetailsActivity.java index 8e967b68b..d4de8be26 100644 --- a/app/src/main/java/be/ugent/zeus/hydra/association/event/EventDetailsActivity.java +++ b/app/src/main/java/be/ugent/zeus/hydra/association/EventDetailsActivity.java @@ -20,7 +20,7 @@ * SOFTWARE. */ -package be.ugent.zeus.hydra.association.event; +package be.ugent.zeus.hydra.association; import android.content.Context; import android.content.Intent; @@ -43,7 +43,6 @@ import java.time.format.DateTimeFormatter; import be.ugent.zeus.hydra.R; -import be.ugent.zeus.hydra.association.Association; import be.ugent.zeus.hydra.common.reporting.BaseEvents; import be.ugent.zeus.hydra.common.reporting.Reporting; import be.ugent.zeus.hydra.common.ui.BaseActivity; @@ -86,39 +85,39 @@ protected void onCreate(Bundle savedInstanceState) { assert event != null; assert association != null; - if (event.getTitle() != null) { - requireToolbar().setTitle(event.getTitle()); + if (event.title() != null) { + requireToolbar().setTitle(event.title()); } - if (event.getAssociation() != null) { - binding.eventOrganisatorMain.setText(association.getName()); + if (event.association() != null) { + binding.eventOrganisatorMain.setText(association.name()); } - if (event.getDescription() != null && !event.getDescription().trim().isEmpty()) { - binding.description.setText(event.getDescription()); + if (event.description() != null && !event.description().trim().isEmpty()) { + binding.description.setText(event.description()); LinkifyCompat.addLinks(binding.description, Linkify.EMAIL_ADDRESSES | Linkify.WEB_URLS); } else { hasDescription = false; binding.eventDescriptionBlock.setVisibility(View.GONE); } - if (association.getDescription() != null && !association.getDescription().trim().isEmpty()) { - binding.eventOrganisatorSmall.setText(association.getDescription()); + if (association.description() != null && !association.description().trim().isEmpty()) { + binding.eventOrganisatorSmall.setText(association.description()); // If there is no event description, allow the association description to be longer. if (!hasDescription) { binding.eventOrganisatorSmall.setMaxLines(Integer.MAX_VALUE); } } - if (association.getWebsite() != null) { - binding.eventOrganizer.setOnClickListener(v -> NetworkUtils.maybeLaunchBrowser(v.getContext(), association.getWebsite())); + if (association.website() != null) { + binding.eventOrganizer.setOnClickListener(v -> NetworkUtils.maybeLaunchBrowser(v.getContext(), association.website())); } if (event.hasPreciseLocation() || event.hasLocation()) { if (event.hasLocation()) { - binding.location.setText(event.getLocation()); + binding.location.setText(event.location()); } else { - binding.location.setText(event.getAddress()); + binding.location.setText(event.address()); } // Make location clickable binding.locationRow.setOnClickListener(view -> NetworkUtils.maybeLaunchIntent(this, getLocationIntent())); @@ -126,16 +125,17 @@ protected void onCreate(Bundle savedInstanceState) { binding.location.setText(R.string.event_detail_no_location); } - binding.timeStart.setText(event.getLocalStart().format(format)); + binding.timeStart.setText(event.localStart().format(format)); - if (event.getLocalEnd() != null) { - binding.timeEnd.setText(event.getLocalEnd().format(format)); + var localEnd = event.localEnd(); + if (localEnd != null) { + binding.timeEnd.setText(localEnd.format(format)); } else { binding.timeEnd.setText(R.string.event_detail_date_unknown); } - if (event.getAssociation() != null) { - Picasso.get().load(association.getImageLink()).into(binding.eventOrganisatorImage, new EventCallback(binding.eventOrganisatorImage)); + if (event.association() != null) { + Picasso.get().load(association.logo()).into(binding.eventOrganisatorImage, new EventCallback(binding.eventOrganisatorImage)); } else { binding.eventOrganisatorImage.setLayoutParams(new LinearLayout.LayoutParams(0, 0)); } @@ -152,8 +152,8 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { supportFinishAfterTransition(); return true; } else if (itemId == R.id.event_link) { - NetworkUtils.maybeLaunchBrowser(this, event.getUrl()); - return true; + NetworkUtils.maybeLaunchBrowser(this, event.url()); + return true; } else if (itemId == R.id.menu_event_add_to_calendar) { addToCalendar(); @@ -180,14 +180,14 @@ public boolean onCreateOptionsMenu(Menu menu) { private void addToCalendar() { Intent intent = new Intent(Intent.ACTION_INSERT) .setData(CalendarContract.Events.CONTENT_URI) - .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, event.getStart().toInstant().toEpochMilli()) - .putExtra(CalendarContract.Events.TITLE, event.getTitle()) - .putExtra(CalendarContract.Events.EVENT_LOCATION, event.getLocation()) - .putExtra(CalendarContract.Events.DESCRIPTION, event.getDescription()) + .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, event.start().toInstant().toEpochMilli()) + .putExtra(CalendarContract.Events.TITLE, event.title()) + .putExtra(CalendarContract.Events.EVENT_LOCATION, event.location()) + .putExtra(CalendarContract.Events.DESCRIPTION, event.description()) .putExtra(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_TENTATIVE); - if (event.getEnd() != null) { - intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, event.getEnd().toInstant().toEpochMilli()); + if (event.end() != null) { + intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, event.end().toInstant().toEpochMilli()); } NetworkUtils.maybeLaunchIntent(this, intent); @@ -218,9 +218,9 @@ private Intent getLocationIntent() { //If there is a precise location, use that. if (event.hasPreciseLocation()) { - uriLocation = Uri.parse("geo:" + GENT + "?q=" + event.getAddress()); + uriLocation = Uri.parse("geo:" + GENT + "?q=" + event.address()); } else { - uriLocation = Uri.parse("geo:" + GENT + "?q=" + event.getLocation()); + uriLocation = Uri.parse("geo:" + GENT + "?q=" + event.location()); } Intent intent = new Intent(Intent.ACTION_VIEW, uriLocation); @@ -244,27 +244,21 @@ public void onError(Exception e) { } } - private static final class EventViewedEvent implements be.ugent.zeus.hydra.common.reporting.Event { - - private final Event event; - - private EventViewedEvent(Event event) { - this.event = event; - } + private record EventViewedEvent(Event event) implements be.ugent.zeus.hydra.common.reporting.Event { @Override - public Bundle getParams() { + public Bundle params() { BaseEvents.Params names = Reporting.getEvents().params(); Bundle params = new Bundle(); params.putString(names.itemCategory(), Event.class.getSimpleName()); - params.putString(names.itemId(), event.getIdentifier()); - params.putString(names.itemName(), event.getTitle()); + params.putString(names.itemId(), event.identifier()); + params.putString(names.itemName(), event.title()); return params; } @Nullable @Override - public String getEventName() { + public String eventName() { return Reporting.getEvents().viewItem(); } } diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/common/EventFilter.java b/app/src/main/java/be/ugent/zeus/hydra/association/EventFilter.java similarity index 85% rename from app/src/main/java/be/ugent/zeus/hydra/association/common/EventFilter.java rename to app/src/main/java/be/ugent/zeus/hydra/association/EventFilter.java index 9da4b129a..74a41c4a3 100644 --- a/app/src/main/java/be/ugent/zeus/hydra/association/common/EventFilter.java +++ b/app/src/main/java/be/ugent/zeus/hydra/association/EventFilter.java @@ -20,7 +20,7 @@ * SOFTWARE. */ -package be.ugent.zeus.hydra.association.common; +package be.ugent.zeus.hydra.association; import android.content.Context; import android.util.Pair; @@ -33,8 +33,6 @@ import java.util.Set; import java.util.function.Function; -import be.ugent.zeus.hydra.association.Association; - /** * Search filter for event requests. * @@ -49,15 +47,15 @@ public class EventFilter { public EventFilter() { } - public OffsetDateTime getAfter() { + public OffsetDateTime after() { return after; } - public OffsetDateTime getBefore() { + public OffsetDateTime before() { return before; } - public String getTerm() { + public String term() { return term; } @@ -83,22 +81,22 @@ public Live() { this.filter = getValue(); } - public void setAfter(OffsetDateTime after) { + public void after(OffsetDateTime after) { this.filter.after = after; setValue(filter); } - public void setBefore(OffsetDateTime before) { + public void before(OffsetDateTime before) { this.filter.before = before; setValue(filter); } - public void setSelectedAssociations(List> selectedAssociations) { + public void selectedAssociations(List> selectedAssociations) { this.filter.selectedAssociations = selectedAssociations; setValue(filter); } - public void setTerm(String term) { + public void term(String term) { this.filter.term = term; setValue(filter); } @@ -107,6 +105,6 @@ public void setTerm(String term) { public static Comparator> selectionComparator() { return Comparator.comparing((Function, Boolean>) p -> p.second) .reversed() - .thenComparing(p -> p.first.getAbbreviation()); + .thenComparing(p -> p.first.abbreviation()); } } diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/common/EventItem.java b/app/src/main/java/be/ugent/zeus/hydra/association/EventItem.java similarity index 50% rename from app/src/main/java/be/ugent/zeus/hydra/association/common/EventItem.java rename to app/src/main/java/be/ugent/zeus/hydra/association/EventItem.java index d77fec757..e73a5788f 100644 --- a/app/src/main/java/be/ugent/zeus/hydra/association/common/EventItem.java +++ b/app/src/main/java/be/ugent/zeus/hydra/association/EventItem.java @@ -20,40 +20,42 @@ * SOFTWARE. */ -package be.ugent.zeus.hydra.association.common; +package be.ugent.zeus.hydra.association; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.ZoneId; -import java.util.Objects; - -import be.ugent.zeus.hydra.association.event.Event; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** - * Data structure for a list of events. The contains an item, a header or a footer, but only one of the elements. + * Data structure for a list of events. + * The event contains an item, a header or a footer, but only one of the elements. * If it is an item, it contains some additional metadata. * * @author Niko Strijbol */ -public final class EventItem implements Comparable { - - private final Event event; - private final LocalDate header; - - private boolean isLastOfSection; - - private EventItem(Event event, LocalDate header) { - this.event = event; - this.header = header; +public record EventItem( + @Nullable Event event, + @Nullable LocalDate header, + boolean isLastOfSection +) implements Comparable { + + public static EventItem create(Event event) { + return new EventItem(event, null, false); } - public EventItem(Event event, boolean isLastOfSection) { - this(event, null); - this.isLastOfSection = isLastOfSection; + public static EventItem create(LocalDate header) { + return new EventItem(null, header, false); } - public EventItem(LocalDate header) { - this(null, header); + public static EventItem create(Event event, boolean isLastOfSection) { + return new EventItem(event, null, isLastOfSection); } public boolean isHeader() { @@ -64,6 +66,7 @@ public boolean isItem() { return event != null; } + @Override public boolean isLastOfSection() { if (!isItem()) { throw new IllegalStateException("Can only be used if the EventItem contains an item."); @@ -71,45 +74,57 @@ public boolean isLastOfSection() { return isLastOfSection; } - public Event getItem() { + @Override + public Event event() { if (!isItem()) { throw new IllegalStateException("Can only be used if the EventItem contains an item."); } return event; } - public LocalDate getHeader() { + @Override + public LocalDate header() { if (!isHeader()) { throw new IllegalStateException("Can only be used if the EventItem contains a header."); } return header; } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - EventItem eventItem = (EventItem) o; - return isLastOfSection == eventItem.isLastOfSection && - Objects.equals(event, eventItem.event) && - Objects.equals(header, eventItem.header); - } - - @Override - public int hashCode() { - return Objects.hash(event, header, isLastOfSection); - } - - public OffsetDateTime getDate() { + public OffsetDateTime date() { if (isItem()) { - return getItem().getStart(); + return event().start(); } else { - return getHeader().atStartOfDay(ZoneId.systemDefault()).toOffsetDateTime(); + return header().atStartOfDay(ZoneId.systemDefault()).toOffsetDateTime(); } } @Override public int compareTo(EventItem o) { - return getDate().compareTo(o.getDate()); + return date().compareTo(o.date()); + } + + @NonNull + public static List fromEvents(@NonNull List events) { + if (events.isEmpty()) { + return Collections.emptyList(); + } + + return events.stream() + .collect(Collectors.groupingBy(event -> event.start().toLocalDate())) + .entrySet() + .stream() + .flatMap(entry -> { + List list = entry.getValue(); + EventItem header = EventItem.create(entry.getKey()); + Stream events1 = list.subList(0, list.size() - 1).stream().map(EventItem::create); + EventItem last = EventItem.create(list.get(list.size() - 1), true); + + return Stream.concat( + Stream.of(header), + Stream.concat(events1, Stream.of(last)) + ); + }) + .sorted() + .collect(Collectors.toList()); } } diff --git a/app/src/test/java/be/ugent/zeus/hydra/resto/RestoMenuTest.java b/app/src/main/java/be/ugent/zeus/hydra/association/EventList.java similarity index 84% rename from app/src/test/java/be/ugent/zeus/hydra/resto/RestoMenuTest.java rename to app/src/main/java/be/ugent/zeus/hydra/association/EventList.java index dabb160fe..c3016dc85 100644 --- a/app/src/test/java/be/ugent/zeus/hydra/resto/RestoMenuTest.java +++ b/app/src/main/java/be/ugent/zeus/hydra/association/EventList.java @@ -20,15 +20,10 @@ * SOFTWARE. */ -package be.ugent.zeus.hydra.resto; - -import be.ugent.zeus.hydra.common.ModelTest; +package be.ugent.zeus.hydra.association; /** * @author Niko Strijbol */ -public class RestoMenuTest extends ModelTest { - public RestoMenuTest() { - super(RestoMenu.class); - } -} \ No newline at end of file +public record EventList(EventPage page) { +} diff --git a/app/src/test/java/be/ugent/zeus/hydra/resto/RestoMealTest.java b/app/src/main/java/be/ugent/zeus/hydra/association/EventPage.java similarity index 84% rename from app/src/test/java/be/ugent/zeus/hydra/resto/RestoMealTest.java rename to app/src/main/java/be/ugent/zeus/hydra/association/EventPage.java index 83045e441..2d035fc01 100644 --- a/app/src/test/java/be/ugent/zeus/hydra/resto/RestoMealTest.java +++ b/app/src/main/java/be/ugent/zeus/hydra/association/EventPage.java @@ -20,15 +20,12 @@ * SOFTWARE. */ -package be.ugent.zeus.hydra.resto; +package be.ugent.zeus.hydra.association; -import be.ugent.zeus.hydra.common.ModelTest; +import java.util.List; /** * @author Niko Strijbol */ -public class RestoMealTest extends ModelTest { - public RestoMealTest() { - super(RestoMeal.class); - } -} \ No newline at end of file +public record EventPage(List entries) { +} diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/common/EventRequest.java b/app/src/main/java/be/ugent/zeus/hydra/association/EventRequest.java similarity index 84% rename from app/src/main/java/be/ugent/zeus/hydra/association/common/EventRequest.java rename to app/src/main/java/be/ugent/zeus/hydra/association/EventRequest.java index 3ecc7fb09..09b0db12c 100644 --- a/app/src/main/java/be/ugent/zeus/hydra/association/common/EventRequest.java +++ b/app/src/main/java/be/ugent/zeus/hydra/association/EventRequest.java @@ -20,7 +20,7 @@ * SOFTWARE. */ -package be.ugent.zeus.hydra.association.common; +package be.ugent.zeus.hydra.association; import android.content.Context; import android.net.Uri; @@ -33,8 +33,6 @@ import java.util.List; import java.util.Set; -import be.ugent.zeus.hydra.association.event.Event; -import be.ugent.zeus.hydra.association.event.EventList; import be.ugent.zeus.hydra.common.network.Endpoints; import be.ugent.zeus.hydra.common.network.JsonOkHttpRequest; import be.ugent.zeus.hydra.common.request.Request; @@ -85,25 +83,19 @@ public Set getRequestedAssociations() { private final Filter filter; - public EventRequest(Context context, Filter filter) { + private EventRequest(Context context, Filter filter) { super(context, EventList.class); this.filter = filter; } - public static Request> createItemRequest(Context context, Filter filter) { + public static Request> eventRequest(Context context, Filter filter) { return new EventRequest(context, filter) - .map(e -> e.getPage().getEntries()) - .map(new EventListConverter()); - } - - public static Request> createRequest(Context context, Filter filter) { - return new EventRequest(context, filter) - .map(e -> e.getPage().getEntries()); + .map(e -> e.page().entries()); } @NonNull @Override - protected String getAPIUrl() { + protected String apiUrl() { Uri.Builder uri = Uri.parse(Endpoints.DSA_V4 + FILENAME).buildUpon(); for (String association : filter.requestedAssociations) { @@ -125,12 +117,12 @@ protected String getAPIUrl() { } String t = uri.appendQueryParameter("page_size", "50").build().toString(); - Log.d("TAG", "getAPIUrl: " + t); + Log.d("TAG", "apiUrl: " + t); return t; } @Override - public Duration getCacheDuration() { + public Duration cacheDuration() { return Duration.ofHours(1); } } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/common/AssociationRequestBuilder.java b/app/src/main/java/be/ugent/zeus/hydra/association/common/AssociationRequestBuilder.java deleted file mode 100644 index 387b84959..000000000 --- a/app/src/main/java/be/ugent/zeus/hydra/association/common/AssociationRequestBuilder.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2022 The Hydra authors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package be.ugent.zeus.hydra.association.common; - -import android.content.Context; -import android.util.Pair; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import be.ugent.zeus.hydra.association.Association; -import be.ugent.zeus.hydra.association.event.Event; -import be.ugent.zeus.hydra.common.network.Endpoints; -import be.ugent.zeus.hydra.common.network.JsonOkHttpRequest; -import be.ugent.zeus.hydra.common.request.Request; -import com.squareup.moshi.Json; - -/** - * Builder to create various requests to get associations from the DSA. - * - * @author Niko Strijbol - */ -public class AssociationRequestBuilder { - - /** - * Raw representation of how the data comes to use from the API. - */ - @VisibleForTesting - static class AssociationList { - @Json(name = "associations") - private List associations; - - public AssociationList() { - } - - @NonNull - public List getAssociations() { - if (associations == null) { - return Collections.emptyList(); - } else { - return associations; - } - } - } - - private static class Mapper implements AssociationMap { - - private final Map associationMap; - private final Set associationRequested; - - private Mapper(@NonNull AssociationRequestBuilder.AssociationList list, @NonNull Set requestedAssociations) { - this.associationMap = list.getAssociations() - .stream() - .collect(Collectors.toMap(Association::getAbbreviation, Function.identity())); - this.associationRequested = requestedAssociations; - } - - @NonNull - @Override - public Association get(@Nullable String abbreviation) { - return associationMap.computeIfAbsent(abbreviation, Association::unknown); - } - - @Override - public Stream associations() { - return associationMap.values().stream(); - } - - @Override - public boolean isRequested(@NonNull String abbreviation) { - return associationRequested.contains(abbreviation); - } - } - - @VisibleForTesting - static class RawRequest extends JsonOkHttpRequest { - private static final String FILENAME = "verenigingen"; - - RawRequest(@NonNull Context context) { - super(context, AssociationList.class); - } - - @NonNull - @Override - protected String getAPIUrl() { - return Endpoints.DSA_V4 + FILENAME; - } - - @Override - public Duration getCacheDuration() { - return ChronoUnit.WEEKS.getDuration().multipliedBy(4); - } - } - - @NonNull - public static Request> createListRequest(@NonNull Context context) { - return new RawRequest(context) - .map(AssociationList::getAssociations); - } - - public static Request>> createItemFilteredEventRequest(@NonNull Context context, EventFilter filter) { - return new RawRequest(context) - .andThen((Function, Set>>>) data -> { - EventRequest.Filter newFilter = filter.toRequestFilter(context, data.getAssociations()); - Set requestedAssociations = newFilter.getRequestedAssociations(); - return EventRequest.createItemRequest(context, newFilter).map(e -> Pair.create(e, requestedAssociations)); - }) - .map(pair -> { - AssociationMap map = new Mapper(pair.first, pair.second.second); - return Pair.create(map, pair.second.first); - }); - } - - public static Request>> createFilteredEventRequest(@NonNull Context context) { - return new RawRequest(context) - .andThen((Function, Set>>>) data -> { - EventFilter eventFilter = new EventFilter(); - EventRequest.Filter newFilter = eventFilter.toRequestFilter(context, data.getAssociations()); - Set requestedAssociations = newFilter.getRequestedAssociations(); - return EventRequest.createRequest(context, newFilter).map(e -> Pair.create(e, requestedAssociations)); - }) - .map(pair -> { - AssociationMap map = new Mapper(pair.first, pair.second.second); - return Pair.create(map, pair.second.first); - }); - } -} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/common/EventListConverter.java b/app/src/main/java/be/ugent/zeus/hydra/association/common/EventListConverter.java deleted file mode 100644 index 162cd8aff..000000000 --- a/app/src/main/java/be/ugent/zeus/hydra/association/common/EventListConverter.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2021 The Hydra authors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package be.ugent.zeus.hydra.association.common; - -import java.time.LocalDate; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import be.ugent.zeus.hydra.association.event.Event; - -/** - * Convert events to EventItems. The list of events MUST be sorted by start date. - */ -class EventListConverter implements Function, List> { - - @Override - public List apply(List events) { - - if (events.isEmpty()) { - return Collections.emptyList(); - } - - return events.stream() - .collect(Collectors.groupingBy(event -> event.getStart().toLocalDate())) - .entrySet() - .stream() - .flatMap(this::convert) - .sorted() - .collect(Collectors.toList()); - } - - private Stream convert(Map.Entry> entry) { - List list = entry.getValue(); - EventItem header = new EventItem(entry.getKey()); - Stream events = list.subList(0, list.size() - 1).stream().map(event -> new EventItem(event, false)); - EventItem last = new EventItem(list.get(list.size() - 1), true); - - return Stream.concat( - Stream.of(header), - Stream.concat(events, Stream.of(last)) - ); - } -} diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/event/EventSorter.java b/app/src/main/java/be/ugent/zeus/hydra/association/event/EventSorter.java deleted file mode 100644 index df9c02361..000000000 --- a/app/src/main/java/be/ugent/zeus/hydra/association/event/EventSorter.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021 The Hydra authors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package be.ugent.zeus.hydra.association.event; - -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -/** - * Sorts the events according to their natural ordening. - * - * @author Niko Strijbol - */ -class EventSorter implements Function, List> { - @Override - public List apply(List events) { - return events.stream() - .sorted() - .collect(Collectors.toList()); - } -} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/list/AssociationsAdapter.java b/app/src/main/java/be/ugent/zeus/hydra/association/list/AssociationAdapter.java similarity index 96% rename from app/src/main/java/be/ugent/zeus/hydra/association/list/AssociationsAdapter.java rename to app/src/main/java/be/ugent/zeus/hydra/association/list/AssociationAdapter.java index acc3a6b41..3da10d0c4 100644 --- a/app/src/main/java/be/ugent/zeus/hydra/association/list/AssociationsAdapter.java +++ b/app/src/main/java/be/ugent/zeus/hydra/association/list/AssociationAdapter.java @@ -33,7 +33,7 @@ /** * @author Niko Strijbol */ -class AssociationsAdapter extends MultiSelectAdapter { +class AssociationAdapter extends MultiSelectAdapter { @NonNull @Override diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/list/AssociationViewHolder.java b/app/src/main/java/be/ugent/zeus/hydra/association/list/AssociationViewHolder.java index 5e81b168a..350a30454 100644 --- a/app/src/main/java/be/ugent/zeus/hydra/association/list/AssociationViewHolder.java +++ b/app/src/main/java/be/ugent/zeus/hydra/association/list/AssociationViewHolder.java @@ -58,7 +58,7 @@ class AssociationViewHolder extends DataViewHolder { @Override public void populate(Association data) { - title.setText(data.getName()); + title.setText(data.name()); checkBox.setChecked(adapter.isChecked(getBindingAdapterPosition())); parent.setOnClickListener(v -> { adapter.setChecked(getBindingAdapterPosition()); diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/list/DateHeaderViewHolder.java b/app/src/main/java/be/ugent/zeus/hydra/association/list/DateHeaderViewHolder.java index 21d62fd7f..f7032ffee 100644 --- a/app/src/main/java/be/ugent/zeus/hydra/association/list/DateHeaderViewHolder.java +++ b/app/src/main/java/be/ugent/zeus/hydra/association/list/DateHeaderViewHolder.java @@ -30,7 +30,7 @@ import java.time.LocalDate; import be.ugent.zeus.hydra.R; -import be.ugent.zeus.hydra.association.common.EventItem; +import be.ugent.zeus.hydra.association.EventItem; import be.ugent.zeus.hydra.common.ui.recyclerview.viewholders.DataViewHolder; @@ -55,6 +55,6 @@ static String formatDate(Context context, LocalDate localDate) { @Override public void populate(EventItem eventItem) { - headerText.setText(formatDate(headerText.getContext(), eventItem.getHeader())); + headerText.setText(formatDate(headerText.getContext(), eventItem.header())); } } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/list/EventAdapter.java b/app/src/main/java/be/ugent/zeus/hydra/association/list/EventAdapter.java index aebc2bafc..6c1871b1e 100644 --- a/app/src/main/java/be/ugent/zeus/hydra/association/list/EventAdapter.java +++ b/app/src/main/java/be/ugent/zeus/hydra/association/list/EventAdapter.java @@ -28,8 +28,8 @@ import java.util.Objects; import be.ugent.zeus.hydra.R; -import be.ugent.zeus.hydra.association.common.AssociationMap; -import be.ugent.zeus.hydra.association.common.EventItem; +import be.ugent.zeus.hydra.association.AssociationMap; +import be.ugent.zeus.hydra.association.EventItem; import be.ugent.zeus.hydra.common.ui.recyclerview.adapters.DiffAdapter; import be.ugent.zeus.hydra.common.ui.recyclerview.viewholders.DataViewHolder; import be.ugent.zeus.hydra.common.utils.ViewUtils; @@ -63,7 +63,7 @@ public DataViewHolder onCreateViewHolder(@NonNull ViewGroup parent, i @Override public int getItemViewType(int position) { - if (getItem(position).isHeader()) { + if (item(position).isHeader()) { return HEADER_TYPE; } else { return super.getItemViewType(position); diff --git a/app/src/main/java/be/ugent/zeus/hydra/association/list/EventFragment.java b/app/src/main/java/be/ugent/zeus/hydra/association/list/EventFragment.java index 590beeb0f..2604a004f 100644 --- a/app/src/main/java/be/ugent/zeus/hydra/association/list/EventFragment.java +++ b/app/src/main/java/be/ugent/zeus/hydra/association/list/EventFragment.java @@ -24,7 +24,6 @@ import android.os.Bundle; import android.util.Log; -import android.util.Pair; import android.view.*; import android.widget.Button; import android.widget.FrameLayout; @@ -43,14 +42,12 @@ import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneOffset; -import java.util.List; import java.util.Objects; import be.ugent.zeus.hydra.MainActivity; import be.ugent.zeus.hydra.R; -import be.ugent.zeus.hydra.association.common.AssociationMap; -import be.ugent.zeus.hydra.association.common.EventFilter; -import be.ugent.zeus.hydra.association.common.EventItem; +import be.ugent.zeus.hydra.association.AssociationRequest; +import be.ugent.zeus.hydra.association.EventFilter; import be.ugent.zeus.hydra.common.arch.observers.PartialErrorObserver; import be.ugent.zeus.hydra.common.arch.observers.ProgressObserver; import be.ugent.zeus.hydra.common.arch.observers.SuccessObserver; @@ -77,7 +74,7 @@ public class EventFragment extends Fragment implements MainActivity.ScheduledRem private final EventAdapter adapter = new EventAdapter(); private final EventFilter.Live filter = new EventFilter.Live(); - private final AssociationsAdapter associationAdapter = new AssociationsAdapter(); + private final AssociationAdapter associationAdapter = new AssociationAdapter(); private EventViewModel viewModel; private FrameLayout bottomSheet; private BottomSheetBehavior behavior; @@ -140,7 +137,7 @@ public boolean onMenuItemSelected(@NonNull @NotNull MenuItem menuItem) { .build(); picker.addOnPositiveButtonClickListener(selection -> { OffsetDateTime offsetDateTime = Instant.ofEpochMilli(selection).atOffset(ZoneOffset.UTC); - filter.setAfter(offsetDateTime); + filter.after(offsetDateTime); }); picker.show(getChildFragmentManager(), picker.toString()); }); @@ -152,7 +149,7 @@ public boolean onMenuItemSelected(@NonNull @NotNull MenuItem menuItem) { .build(); picker.addOnPositiveButtonClickListener(selection -> { OffsetDateTime offsetDateTime = Instant.ofEpochMilli(selection).atOffset(ZoneOffset.UTC); - filter.setBefore(offsetDateTime); + filter.before(offsetDateTime); }); picker.show(getChildFragmentManager(), picker.toString()); }); @@ -179,18 +176,18 @@ public boolean onMenuItemSelected(@NonNull @NotNull MenuItem menuItem) { swipeRefreshLayout.setColorSchemeColors(secondaryColour); viewModel = new ViewModelProvider(this).get(EventViewModel.class); - viewModel.setParams(filter.getValue()); - viewModel.getData().observe(getViewLifecycleOwner(), PartialErrorObserver.with(this::onError)); - viewModel.getData().observe(getViewLifecycleOwner(), new ProgressObserver<>(view.findViewById(R.id.progress_bar))); - viewModel.getData().observe(getViewLifecycleOwner(), new SuccessObserver>>() { + viewModel.params(filter.getValue()); + viewModel.data().observe(getViewLifecycleOwner(), PartialErrorObserver.with(this::onError)); + viewModel.data().observe(getViewLifecycleOwner(), new ProgressObserver<>(view.findViewById(R.id.progress_bar))); + viewModel.data().observe(getViewLifecycleOwner(), new SuccessObserver<>() { @Override - protected void onSuccess(@NonNull Pair> data) { - adapter.setAssociationMap(data.first); - adapter.submitData(data.second); - associationAdapter.setItemsAndState(data.first.getSelectedAssociations()); + protected void onSuccess(@NonNull AssociationRequest.EventItemsAndAssociations data) { + adapter.setAssociationMap(data.associations()); + adapter.submitData(data.events()); + associationAdapter.itemsAndState(data.associations().requestedAssociations()); } }); - viewModel.getRefreshing().observe(getViewLifecycleOwner(), swipeRefreshLayout::setRefreshing); + viewModel.refreshing().observe(getViewLifecycleOwner(), swipeRefreshLayout::setRefreshing); swipeRefreshLayout.setOnRefreshListener(viewModel); view.