SDPepe/AppArt

View on GitHub
app/src/main/java/ch/epfl/sdp/appart/AdActivity.java

Summary

Maintainability
A
3 hrs
Test Coverage
C
79%
package ch.epfl.sdp.appart;

import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.res.ResourcesCompat;
import androidx.lifecycle.ViewModelProvider;

import com.bumptech.glide.Glide;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import javax.inject.Inject;

import ch.epfl.sdp.appart.ad.AdViewModel;
import ch.epfl.sdp.appart.database.DatabaseService;
import ch.epfl.sdp.appart.database.firebaselayout.FirebaseLayout;
import ch.epfl.sdp.appart.glide.visitor.GlideImageViewLoader;
import ch.epfl.sdp.appart.login.LoginService;
import ch.epfl.sdp.appart.scrolling.card.Card;
import ch.epfl.sdp.appart.user.User;
import ch.epfl.sdp.appart.utils.ActivityCommunicationLayout;
import ch.epfl.sdp.appart.utils.DatabaseSync;
import dagger.hilt.android.AndroidEntryPoint;

import static android.widget.Toast.makeText;

/**
 * This class manages the UI of the ad.
 */
@AndroidEntryPoint
public class AdActivity extends ToolbarActivity {

    @Inject
    DatabaseService database;
    @Inject
    LoginService login;

    public static class Intents {
        public static final String INTENT_PANORAMA_PICTURES =
                "panoramas_pictures_references";
        public static final String INTENT_AD_ID = "adId";
    }


    private String advertiserId;
    private Pair<ArrayList<String>, Boolean> panoramasReferences;

    private String adId;
    private AdViewModel mViewModel;

    private Button panoramaButton;
    private Button contactButton;
    private Button seeLocationButton;
    private Button seeNearbyPlacesButton;
    private MenuItem addFavoriteItem;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_announce);

        addFavoriteItem = findViewById(R.id.action_add_favorite);

        /**
         * Retrieve buttons
         */
        panoramaButton = findViewById(R.id.vtour_Ad_button);
        contactButton = findViewById(R.id.contact_info_Ad_button);
        seeLocationButton = findViewById(R.id.seeLocation_Ad_button);
        seeNearbyPlacesButton = findViewById(R.id.seeNearbyPlaces_Ad_button);

        panoramaButton.setOnClickListener(this::noInformationOnClickListener);
        contactButton.setOnClickListener(this::noInformationOnClickListener);
        seeLocationButton.setOnClickListener(this::noInformationOnClickListener);
        seeNearbyPlacesButton.setOnClickListener(this::noInformationOnClickListener);


        panoramasReferences = new Pair(new ArrayList<>(), false);
        mViewModel = new ViewModelProvider(this).get(AdViewModel.class);

        Toolbar toolbar = findViewById(R.id.account_Ad_toolbar);
        setSupportActionBar(toolbar);

        panoramaButton = findViewById(R.id.vtour_Ad_button);
        panoramaButton.setVisibility(View.INVISIBLE);

        mViewModel.getTitle().observe(this, this::updateTitle);
        mViewModel.getPhotosRefs().observe(this, this::updatePhotos);
        mViewModel.getAddress().observe(this, this::updateAddress);
        mViewModel.getPrice().observe(this, this::updatePrice);
        mViewModel.getDescription().observe(this, this::updateDescription);
        mViewModel.getAdAdvertiserName().observe(this,
                this::updateAdvertiserName);
        mViewModel.getAdvertiserId().observe(this, this::updateAdvertiserId);
        mViewModel.observePanoramasReferences(this,
                this::updatePanoramasReferences);

        mViewModel.getHasVTour().observe(this, this::updateHasVTourButton);
        mViewModel.getHasLoaded().observe(this, this::setNormalClickListeners);

        adId = getIntent().getStringExtra(ActivityCommunicationLayout.PROVIDING_AD_ID);
        // init content, show a toast if load failed
        mViewModel.initAd(adId)
                .exceptionally(e -> {
                    Log.d("AD", "Failed to fetch data from server");
                    return null;
                });

        //updateBookmarkIconState();
    }

    public void noInformationOnClickListener(View view) {
        Snackbar.make(view, R.string.buttonInfoMessageAdNotLoaded,
                BaseTransientBottomBar.LENGTH_SHORT).show();
    }

    private void setNormalClickListeners(Boolean hasLoaded) {
        panoramaButton.setOnClickListener(this::openVirtualTour);
        contactButton.setOnClickListener(this::openContactInfo);
        seeLocationButton.setOnClickListener(this::onSeeLocationClick);
        seeNearbyPlacesButton.setOnClickListener(this::onNearbyPlacesClick);
    }

    private void updateTitle(String title) {
        TextView titleView = findViewById(R.id.title_Ad_textView);
        setIfNotNull(titleView, title);
    }

    private void updateHasVTourButton(Boolean hasVTour) {
        if (hasVTour) {
            panoramaButton.setVisibility(View.VISIBLE);
        }
    }

    private void updatePhotos(Pair<List<String>, Boolean> referencesAndIsLocal) {
        LinearLayout horizontalLayout =
                findViewById(R.id.horizontal_children_Ad_linearLayout);
        horizontalLayout.removeAllViews();
        List<String> references = referencesAndIsLocal.first;
        boolean isLocal = referencesAndIsLocal.second;
        for (int i = 0; i < references.size(); i++) {
            String sep = FirebaseLayout.SEPARATOR;
            LayoutInflater inflater =
                    (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View myView = inflater.inflate(R.layout.photo_layout,
                    (ViewGroup) null);
            ImageView photo = myView.findViewById(R.id.photo_Photo_imageView);
            String fullRef;
            if (isLocal) {
                fullRef = references.get(i);
                Bitmap bitmap = BitmapFactory.decodeFile(fullRef);
                Glide.with(this).load(bitmap)
                        .into(photo);
            } else {
                fullRef =
                        FirebaseLayout.ADS_DIRECTORY + sep + adId + sep + references.get(i);
                database.accept(new GlideImageViewLoader(this, photo, fullRef));
            }
            horizontalLayout.addView(myView);

            // open image fullscreen on tap
            myView.setOnClickListener(e -> openImageFullscreen(fullRef,
                    isLocal));
        }
    }

    private void updatePanoramasReferences(Pair<List<String>, Boolean> references) {
        panoramasReferences.first.clear();
        panoramasReferences.first.addAll(references.first);
        panoramasReferences = new Pair<>(panoramasReferences.first,
                references.second);
    }

    private void updateAddress(String address) {
        TextView addressView = findViewById(R.id.address_field_Ad_textView);
        setIfNotNull(addressView, address);
    }

    private void updatePrice(String price) {
        TextView priceView = findViewById(R.id.price_field_Ad_textView);
        setIfNotNull(priceView, price);
    }

    private void updateDescription(String description) {
        TextView descriptionView =
                findViewById(R.id.description_field_Ad_textView);
        setIfNotNull(descriptionView, description);
    }

    private void updateAdvertiserName(String username) {
        TextView usernameView = findViewById(R.id.user_field_Ad_textView);
        setIfNotNull(usernameView, username);
    }

    private void updateAdvertiserId(String advertiserId) {
        this.advertiserId = advertiserId;
    }

    private void setIfNotNull(TextView view, String content) {
        if (content == null) {
            view.setText(R.string.loadingTextAdActivity);
        } else {
            view.setText(content);
        }
    }

    /**
     * Method called when you want open the contact info.
     *
     * @param view
     */
    public void openContactInfo(View view) {
        Intent intent = new Intent(this, SimpleUserProfileActivity.class);
        intent.putExtra(ActivityCommunicationLayout.PROVIDING_USER_ID,
                this.advertiserId);
        startActivity(intent);
    }

    /**
     * Method called when you want open the virtual tour.
     *
     * @param view the view that we never use in this calls
     */
    public void openVirtualTour(View view) {
        Intent intent = new Intent(this, PanoramaActivity.class);
        intent.putStringArrayListExtra(Intents.INTENT_PANORAMA_PICTURES,
                panoramasReferences.first);
        intent.putExtra(Intents.INTENT_AD_ID, adId);
        intent.putExtra("isLocalExtra", panoramasReferences.second);
        startActivity(intent);
    }

    /**
     * Opens the location activity showing the position of this ad
     *
     * @param view the view that we never use in this calls
     */
    public void onSeeLocationClick(View view) {
        Intent intent = new Intent(this, MapActivity.class);
        intent.putExtra(getString(R.string.intentLocationForMap),
                mViewModel.getAddress().getValue());
        startActivity(intent);
    }

    private void openImageFullscreen(String imageId, boolean isLocal) {
        Intent intent = new Intent(this, FullScreenImageActivity.class);
        intent.putExtra("imageId", imageId);
        intent.putExtra("isLocalExtra", isLocal);
        startActivity(intent);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.ad_toolbar, menu);
        addFavoriteItem = menu.getItem(0);
        updateBookmarkIconState();
        return true;
    }

    /**
     * Adds ad to favorites, shows a toast with the action result
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.action_add_favorite) {
            if (!DatabaseSync.areWeOnline(this)) {
                makeText(this, "You can't add or remove favorites while " +
                        "offline !", Toast.LENGTH_SHORT).show();
                return true;
            }

            CompletableFuture<Boolean> addRes = addNewFavorite();
            addRes.exceptionally(e -> {
                Log.d("AD", "Failed to add to favorites");
                return null;
            });
            addRes.thenAccept(res -> {
                        setBookmarkIcon(!res);
                    }
            );
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    /**
     * Check if user is logged in and add new favorite ad to user
     */
    private CompletableFuture<Boolean> addNewFavorite() {
        CompletableFuture<Boolean> result = new CompletableFuture<>();
        // check that the user is logged in
        User user = login.getCurrentUser();
        if (user == null) {
            result.completeExceptionally(new IllegalStateException("User has " +
                    "to be logged in"));
            return result;
        }
        CompletableFuture<User> userRes = database.getUser(user.getUserId());
        userRes.exceptionally(e -> {
            Log.d("AD", "Failed to get user");
            result.completeExceptionally(e);
            return null;
        });
        userRes.thenAccept(u -> saveFavorite(result, u));
        return result;
    }

    /**
     * Adds the ad id to the list of favorites of the user and update the
     * user in the server.
     */
    private void saveFavorite(CompletableFuture<Boolean> result, User user) {
        boolean alreadyAdded = user.getFavoritesIds().contains(adId);
        CompletableFuture<List<Card>> futureCards = database.getCards();
        CompletableFuture<Card> futureLocalUpdate;
        if (alreadyAdded) {
            futureLocalUpdate = futureCards.thenApply(cards -> {
                Card card = getMatchingCard(cards);
                if (card == null) {
                    return card;
                }
                localdb.removeCard(card.getId());
                user.removeFavorite(adId);
                return card;
            });
        } else {
            futureLocalUpdate = futureCards.thenApply(cards -> {
                Card card = getMatchingCard(cards);
                if (card == null) {
                    return card;
                }
                DatabaseSync.writeAd(database, card, this, localdb);
                user.addFavorite(adId);
                return card;
            });
        }
        CompletableFuture<Boolean> updateRes =
                futureLocalUpdate.thenCompose(card -> database.updateUser(user).exceptionally(e -> {
                    if (alreadyAdded) {
                        user.addFavorite(adId);
                    } else {
                        user.removeFavorite(adId);
                    }
                    return null;
                }));
        updateRes.exceptionally(e -> {
            Log.d("AD", "failed to update user");
            result.completeExceptionally(e);
            return null;
        });
        updateRes.thenAccept(res -> {
            Log.d("AD", "user updated");
            localdb.setCurrentUser(user, BitmapFactory.decodeFile(user.getProfileImagePathAndName()));
            result.complete(alreadyAdded);
        });
    }

    private Card getMatchingCard(List<Card> cards) {
        for (Card card : cards) {
            if (card.getAdId().equals(adId)) {
                return card;
            }
        }
        return null;
    }

    public void onNearbyPlacesClick(View view) {
        Intent intent = new Intent(this, PlaceActivity.class);
        intent.putExtra(ActivityCommunicationLayout.AD_ADDRESS,
                mViewModel.getAddress().getValue());
        startActivity(intent);
    }

    private void updateBookmarkIconState() {


        User currentUser = login.getCurrentUser();
        if (currentUser == null) {
            currentUser = localdb.getCurrentUser();
        }


         /*
                This is really slow if we don't check for connectivity.
          */
        if (DatabaseSync.areWeOnline(this)) {
            User finalCurrentUser = currentUser;
            database.getUser(currentUser.getUserId()).thenAccept(user -> {
                setBookmarkIcon(user.getFavoritesIds().contains(adId));
            }).exceptionally(e -> {
                e.printStackTrace();
                updateBookmarkLocalDatabase(finalCurrentUser);
                return null;
            });
        } else {
            updateBookmarkLocalDatabase(currentUser);
        }
    }

    private void updateBookmarkLocalDatabase(User currentUser) {
        setBookmarkIcon(currentUser.getFavoritesIds().contains(adId));

    }

    private void setBookmarkIcon(boolean filled) {
        runOnUiThread(() -> {
            if (filled) {
                addFavoriteItem.setIcon(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_bookmark_filled, null));
            } else {
                addFavoriteItem.setIcon(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_bookmark, null));
            }
        });
    }

}