Skip to content
Alessia Crimaldi edited this page Feb 3, 2023 · 34 revisions

CardExchange Structure Overview

MVP

MVP is a design pattern that separated the logic of a user interface from the business logic. It consists of three main components:

  • Model: represents the business objects.
  • View: responsible for displaying data and handling user interactions. Views have no notion of the model.
  • Presenter: acts as a bridge between the Model and the View. It handles all of the logic for our application.

Activities and Places

In our application has been used a built-in framework for browser history management: the Activity and Places framework. The presenters in our app will be represented by activities and they will have the purpose of containing both the business logic and a set of life cycles of each view. A place is a Java object representing a particular state or location within the application. A Place can be converted to and from a URL history token.

The PlaceController is crucial component for Activities and Places framework. It is responsible for coordinating the navigation between different places in the application. It does this by updating the browser's URL and the browser's history stack, so that the user can use the browser's back and forward buttons to navigate through the app.

The PlaceController also acts as a bridge between the places and the activities. When a user navigates to a new place, the PlaceController will call the appropriate activity to handle the transition to the new place. It also updates the current place and activity, so that the application can keep track of the user's current location.

// todo: spiegare molto meglio la parte del routing, è una parte fondamentale del nostro progetto

HomePage

  • The HomeActivity class serves as the presenter for the HomeView, which is responsible for handling the logic of how the data should be displayed, it receives the data from the service class and pass it to the view to be displayed on the UI. The fetchGameCards method fetches the card data for a specific game from the server and passes it to the view with setData.

  • The HomeViewImpl class is the concrete implementation of the HomeView that is responsible for displaying the data on the UI using GWT widgets. It also has the method that interact with the presenter, such as onValueChanged that handles the radio buttons events. The setData takes the data and displays the data on UI.

// Model: base CardDecorator and concrete decorators for extra behaviors
abstract class CardDecorator {
    CardImpl wrappee;
    
    protected CardDecorator(CardImpl card) {
        wrappee = card;
    }
    // extra behaviors
}

// Presenter: HomeActivity
class HomeActivity extends AbstractActivity implements HomeView.Presenter {
    private final HomeView view;
    private final CardServiceAsync rpcService;

    public HomeActivity(HomeView view, CardServiceAsync rpcService) {
        this.view = view;
        this.rpcService = rpcService;
    }

    public void fetchGameCards(Game game) {
        if (game == null) {
            throw new IllegalArgumentException("game cannot be null");
        }
        rpcService.getGameCards(game, new BaseAsyncCallback<List<CardDecorator>>() {
            @Override
            public void onSuccess(List<CardDecorator> result) {
                view.setData(result);
            }
        });
    }
}

// View: HomeViewImpl
public class HomeViewImpl implements HomeView {
    void setData(List<CardDecorator> data) {
        // show the data
    }

    void onValueChanged(ValueChangeEvent<Boolean> e, Game game) {
        presenter.fetchGameCards(game);
    }
}

CardWidget

image

This class is a visual component that displays a preview of a selected game card with all its details on the Home Page. The widget accepts a "CardDecorator" object as input, which could be of any of the three types of cards (YuGi-Oh, Magic, Pokemon). To display the details of the card, the code uses three switch statements to build a "details" string containing the specific fields for each card type. This way, the class can handle all types of card and show their specific details.

public CardWidget(FunctionInterface parent, CardDecorator card) {
    initWidget(uiBinder.createAndBindUi(this));
    nameDiv.setInnerHTML(card.getName());
    descDiv.setInnerHTML(card.getDescription());
    typeDiv.setInnerHTML(card.getType());

    if (card instanceof YuGiOhCardDecorator) {
        imageDiv.setUrl(((YuGiOhCardDecorator) card).getSmallImageUrl());
        // builds details string for YuGiOhCardDecorator
    }
    if (card instanceof PokemonCardDecorator) {
       // builds details string for PokemonCardDecorator
    }
    if (card instanceof MagicCardDecorator) {
       // builds details string for MagicCardDecorator
    }
    details.setInnerHTML(html);
        
    detailsButton.addClickHandler(clickEvent -> parent.handleClickCard());
}

GameFilters

The homepage contains also GameFiltersWidget, that is a widget that allows filtering a list of cards for a specific game. When a different game is selected, the options available for the filters are changed. For example, if Magic game is selected, options like "Name", "Artist", and "Description" will be displayed. There are also options to filter by special attributes and type. Additionally, there are checkboxes to filter by boolean fields like "hasFoil" or "isPromo".

Screenshot 2023-01-16 alle 14 24 10

The method filterGameCards in the HomePresenter class is responsible for filtering a list of "CardDecorator" objects based on various input parameters. These parameters include a special attribute value, a type value, a text input name, a text input value, a list of boolean input names, and a list of boolean input values.
The method iterates through each "CardDecorator" object in the list and checks if it should be included in the filtered list using a series of if/else statements.

The input parameters are used as follows:

  • The "specialAttributeValue" is used to filter the cards based on a specific attribute, such as rarity for Magic cards or edition for Pokemon cards.
  • The "typeValue" is used to filter the cards based on the type of card, such as Magic or Pokemon.
  • The "textInputName" is used to specify which text field to search for the "textInputValue" in, such as name, description, or artist.
  • The "textInputValue" is used to search for a specific value in the text field specified by "textInputName"
  • The "booleanInputNames" and "booleanInputValues" are used together to filter the cards based on specific boolean attributes, such as "isFirstEdition" or "hasFoil"

If a card meets all the specified criteria, it is added to the "filteredCards" list and returned at the end of the method.

public List<CardDecorator> filterGameCards(String specialAttributeValue, String typeValue, String textInputName, String textInputValue,
                                               List<String> booleanInputNames, List<Boolean> booleanInputValues) {
       List<CardDecorator> filteredCards = new ArrayList<>();
       for (CardDecorator card : cards) {
           boolean shouldSkip = false;
           if (!textInputValue.isEmpty()) {
               // logic for filter for textAttribute
               // pseudocode
               if (card not meet the textAttribute criteria) {
                  shouldSkip = true;
               }
           }
           if (!specialAttributeValue.equals("all")) {
               // logic for filter for specialAttribute
               // pseudocode
               if (card not meet the specialAttribute criteria) {
                  shouldSkip = true;
               }
           }
           if (!typeValue.equals("all")) {
               // logic for filter for typeAttribute
               // pseudocode
               if (card not meet the typeAttribute criteria) {
                  shouldSkip = true;
               }
           }
           if (!(booleanInputNames.isEmpty() && booleanInputValues.isEmpty())) {
               // logic for filter for 
               // pseudocode
               if (card not meet the booleanAttributes criteria) {
                  shouldSkip = true;
               }
           }
           if (!shouldSkip) {
               filteredCards.add(card);
           }
       }
       return filteredCards;
}

AuthPage

image

  • The AuthenticationViewImpl class is the concrete implementation of the AuthenticationView. This view aim is to display an UI into which the visitor is able to create his own account and the user can authenticate himself. This is possible by switching the mode from Login to Signup through the Switch to mode button. This class also has a ClickHandler associated with the Login/Signup button, called authButton, which interacts with the authenticate method of the presenter using the enum variable AuthMode. Moreover, the resetFields method resets the email and password fields.

  • The AuthenticationActivity class, instead, works as presenter for the AuthenticationView, which responsability is handling the logic when pressing the Login or the Signup button. The authenticate method makes the user navigate to the HomePage, with the placeController, if there's no error in the fields, or warn the user if an error occurres. In both cases, fields are reset. In fact, the onStop method, calls resetFields when the Activity stops, so when the user leaves the authentication page.

// Enum variable: AuthMode
public enum AuthMode {
    Login,
    Signup
}

// View: AuthenticationViewImpl
public class AuthenticationViewImpl implements AuthenticationView {
    public AuthenticationViewImpl() {
        authButton.addClickHandler((ClickEvent) -> {
            if (authButton.getText().equals(AuthMode.Login.name())) {
                presenter.authenticate(AuthMode.Login, emailField.getText(), passwordField.getText());
            } else {
                presenter.authenticate(AuthMode.Signup, emailField.getText(), passwordField.getText());
            }
        });
    }
 
    public void resetFields() {
        emailField.setText("");
        passwordField.setText("");
    }
}

// Presenter: AuthenticationActivity
public class AuthenticationActivity extends AbstractActivity implements AuthenticationView.Presenter {
    private final AuthenticationView view;
    private final PlaceController placeController;

    public AuthenticationActivity(AuthenticationView view, PlaceController placeController) {
        this.view = view;
        this.placeController = placeController;
    }

    public void onStop() {
        view.resetFields();
    }

    public void authenticate(AuthMode authMode, String email, String password) {
        try {
            if (authMode == AuthMode.Login) {
                // call signin and catch errors
            } else if (authMode == AuthMode.Signup) {
                // call signup and catch errors
            }
            goTo(new HomePlace());
        } catch (Exception e) {
            view.resetFields();
        }
    }

    // placeController
    public void goTo(Place place) {
        placeController.goTo(place);
    }
}

CardDetailsPage

This page, besides reporting the information of the selected card, gives the logged-in user the possibility to add the card to the owned cards deck or to the wished cards deck and shows the lists of users who own it and who wish it. So the page is divided in three maining parts:

  • The card information, which is populated by the CardActivity presenter through the method fetchCard. This method is responsible of making a call to the server to take the selected card through the game and cardId parameters passed in the place, and then to recall the method setData of CardView. So the setData method deals with taking the result, of CardDecorator type, and printing in the interface the information related to each different type of card, ie type MagicCardDecorator, PokemonCardDecorator or YuGiHoCardDecorator.

image

  • The "add to deck" section, which is implemented like a Widget named AddCardToDeckWidget that simply consists of a select, which allows the user to select the deck (Owned or Wished), and of a PushButton, which actually performs the addition. By clicking on the 'Add to deck' button a modal (AddCardToDeckModalWidget) opens in order to let the user insert the properties of the physical card (status and description). Then, this values are sent back to CardView, which handles them and invokes the 'addCardToDeck' method of the presenter CardActivity sending the deck name parameter too. In the end, the presenter is responsible for invoking the addPhysicalCardToDeck method from deckService passing all the requested parameters.

image

  • The users lists, of which one is for the users who own the card (Owned by), and the other is for the users who instead wish it (Wished by). This lists are widgets (UserListWidget) and are created dynamically using GWT FlexTable. Moreover, using this lists, it is possible to propose an exchange to another user.

image

In conclusion, all the action buttons (the 'Add to deck' button and the 'Exchange' button) in this page are not showen to the visitors, while are showen to the logged-in users. This is possible thanks to the createUserWidgets method of the view, which checks if the user is logged-in through the boolean variable isLoggedIn, and the presenter method update with the help of the AuthSubject.

// Presenter: CardActivity
public class CardActivity extends AbstractActivity implements CardView.Presenter, Observer {
    private final CardPlace place;
    private final CardView view;
    private final CardServiceAsync rpcService;
    private final AuthSubject authSubject;

    public CardActivity(CardPlace place, CardView view, CardServiceAsync rpcService, AuthSubject authSubject) {
        this.place = place;
        this.view = view;
        this.rpcService = rpcService;
        this.authSubject = authSubject;
        authSubject.attach(this);
    }

    public void update() {
        view.createUserWidgets(authSubject.isLoggedIn());
    }

    public void fetchCard() {
        rpcService.getGameCard(place.getGame(), place.getCardId(), new BaseAsyncCallback<CardDecorator>() {
            @Override
            public void onSuccess(CardDecorator result) {
                view.setData(result);
            }
        });
    }

    public void addCardToDeck(String deckName, String status, String description) {
        deckService.addPhysicalCardToDeck(
                authSubject.getToken(),
                place.getGame(),
                deckName,
                place.getCardId(),
                Status.getStatus(Integer.parseInt(status)),
                description,
                new AsyncCallback<Boolean>() {
                    @Override
                    public void onFailure(Throwable caught) {
                        // displayErrorAlert method of the view;
                        }
                    }
                    @Override
                    public void onSuccess(Boolean result) {
                        // displaySuccessAlert method of the view
                    }
                }
        );
    }
}

// View: CardViewImpl
public class CardViewImpl extends Composite implements CardView {
    private static final CardsViewImplUIBinder uiBinder = GWT.create(CardsViewImplUIBinder.class);
    @UiField
    HTMLPanel addCardToDeckContainer;
    @UiField
    HTMLPanel userLists;
    Presenter presenter;

    public void setData(CardDecorator data) {
        String gameType = "";
        // other properties declarations
        if (data instanceof MagicCardDecorator) {
            gameType = "Magic";
            // properties checks 
        } else if (data instanceof PokemonCardDecorator) {
            gameType = "Pokemon";
            // properties checks 
        } else if (data instanceof YuGiOhCardDecorator) {
            gameType = "YuGiOh";
            // properties checks 
        }
        // properties assignment to UI
    }

    public void createUserWidgets(boolean isLoggedIn) {
        addCardToDeckContainer.clear();
        userLists.clear();
        // create AddCartToDeckWidget
        if (isLoggedIn) addCardToDeckContainer.add(new AddCardToDeckWidget());
        // create UserListWidget 'Exchange' buttons
        userLists.add(new UserListWidget(
                "Owned by",
                cardHoldersUsers,
                isLoggedIn
        ));
        userLists.add(new UserListWidget(
                "Wished by",
                cardSeekerUsers,
                isLoggedIn));
    }
}
Clone this wiki locally