SDPepe/AppArt

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

Summary

Maintainability
A
0 mins
Test Coverage
A
94%
package ch.epfl.sdp.appart;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;


import javax.inject.Inject;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;

import com.bumptech.glide.Glide;

import java.util.concurrent.CompletableFuture;

import ch.epfl.sdp.appart.database.DatabaseService;
import ch.epfl.sdp.appart.database.firebaselayout.FirebaseLayout;
import ch.epfl.sdp.appart.database.local.LocalDatabaseService;
import ch.epfl.sdp.appart.glide.visitor.GlideBitmapLoader;
import ch.epfl.sdp.appart.glide.visitor.GlideImageViewLoader;
import ch.epfl.sdp.appart.user.Gender;
import ch.epfl.sdp.appart.user.User;
import ch.epfl.sdp.appart.user.UserViewModel;
import ch.epfl.sdp.appart.utils.ActivityCommunicationLayout;
import ch.epfl.sdp.appart.utils.DatabaseSync;
import ch.epfl.sdp.appart.utils.UIUtils;
import dagger.hilt.android.AndroidEntryPoint;

/**
 * This class manages the UI of the user profile, the user may edit
 * its personal information from here. This class then interacts
 * with the user view model which updates the firestore database.
 */
@AndroidEntryPoint
public class UserProfileActivity extends AppCompatActivity {

    /* temporary user */
    private Pair<User, Boolean> sessionUser;

    /* User ViewModel */
    UserViewModel mViewModel;

    @Inject
    DatabaseService database;
    @Inject
    LocalDatabaseService localdb;

    /* UI components */
    private Button modifyButton;
    private Button doneButton;
    private Button changeImageButton;
    private Button removeImageButton;
    private EditText nameEditText;
    private EditText ageEditText;
    private EditText phoneNumberEditText;
    private Spinner genderSpinner;
    private TextView uniAccountClaimer;
    private TextView emailTextView;
    private ImageView imageView;

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

        /* User ViewModel initialization */
        mViewModel = new ViewModelProvider(this).get(UserViewModel.class);

        initUIComponents();
        modifyButton.setVisibility(View.INVISIBLE);

        /* get user from database from user ID */
        CompletableFuture<Void> userRes = mViewModel.getCurrentUser();
        // if user successfully fetched from DB, save it locally
        userRes.exceptionally(e -> {
            // failed to fetch -> prevent updated (we're probably offline)
            modifyButton.setVisibility(View.GONE);
            return null;
        });
        userRes.thenAccept(res ->
                DatabaseSync.saveCurrentUserToLocalDB(this, database, localdb,
                        mViewModel.getUser().getValue().first.getUserId()));
        mViewModel.getUser().observe(this, this::setSessionUserToLocal);
    }

    private void initUIComponents() {
        this.modifyButton = findViewById(R.id.editProfile_UserProfile_button);
        this.doneButton = findViewById(R.id.doneEditing_UserProfile_button);
        this.removeImageButton = findViewById(R.id.removeImage_UserProfile_button);
        this.changeImageButton = findViewById(R.id.editImage_UserProfile_button);
        this.nameEditText = findViewById(R.id.name_UserProfile_editText);
        this.ageEditText = findViewById(R.id.age_UserProfile_editText);
        this.emailTextView = findViewById(R.id.emailText_UserProfile_textView);
        this.phoneNumberEditText = findViewById(R.id.phoneNumber_UserProfile_editText);
        this.genderSpinner = findViewById(R.id.gender_UserProfile_spinner);
        this.genderSpinner.setEnabled(ageEditText.isEnabled());
        this.uniAccountClaimer = findViewById(R.id.uniAccountClaimer_UserProfile_textView);
        this.imageView = findViewById(R.id.profilePicture_UserProfile_imageView);

        this.imageView.setEnabled(false);
        this.removeImageButton.setVisibility(View.GONE);
        this.changeImageButton.setVisibility(View.GONE);
    }

    /**
     * edit user info, called by edit profile button
     */
    public void editProfile(View view) {
        this.modifyButton.setVisibility(View.GONE);
        this.doneButton.setVisibility(View.VISIBLE);
        this.changeImageButton.setVisibility(View.VISIBLE);
        if (!this.sessionUser.first.hasDefaultProfileImage()) {
            this.removeImageButton.setVisibility(View.VISIBLE);
        } else {
            this.removeImageButton.setVisibility(View.INVISIBLE);
        }

        /* enable editing in all UI components */
        enableDisableEntries();
    }

    /**
     * removes the profile image from database and sets default user icon
     * called by the remove button under imageView
     */
    public void removeProfileImage(View view) {
        mViewModel.deleteImage(this.sessionUser.first.getProfileImagePathAndName());
        this.sessionUser.first.setDefaultProfileImage();
        imageView.setImageResource(android.R.color.transparent);
        this.removeImageButton.setVisibility(View.INVISIBLE);
    }

    /**
     * changes the profile image of the user
     * called by the imageView button
     */
    public void changeProfileImage(View view) {
        Intent intent = new Intent(this, CameraActivity.class);
        intent.putExtra(ActivityCommunicationLayout.PROVIDING_ACTIVITY_NAME,
                ActivityCommunicationLayout.USER_PROFILE_ACTIVITY);
        startActivityForResult(intent, 1);
    }


    /**
     * manages the output of camera activity and updates the profile image
     * in firestore and firebase storage using user view-model
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 1) {
            if (resultCode == RESULT_OK) {
                this.doneButton.setEnabled(false);
                Uri profileUri =
                        data.getParcelableExtra(ActivityCommunicationLayout.PROVIDING_IMAGE_URI);
                mViewModel.setUri(profileUri);
                imageView.setImageURI(profileUri);
                mViewModel.deleteImage(this.sessionUser.first.getProfileImagePathAndName());

                StringBuilder imagePathInDb = new StringBuilder();
                imagePathInDb
                        .append(FirebaseLayout.USERS_DIRECTORY)
                        .append(FirebaseLayout.SEPARATOR)
                        .append(this.sessionUser.first.getUserId())
                        .append(FirebaseLayout.SEPARATOR)
                        .append(FirebaseLayout.PROFILE_IMAGE_NAME)
                        .append(System.currentTimeMillis())
                        .append(FirebaseLayout.JPEG);
                // TODO: support more image formats

                this.sessionUser.first.setProfileImagePathAndName(imagePathInDb.toString());
                mViewModel.updateImage(this.sessionUser.first.getUserId());
                mViewModel.getUpdateImageConfirmed().observe(this, this::imageUpdateResult);
            }
        }
    }

    /**
     * saves updated user information in firestore database, called by the done button
     */
    public void doneEditing(View view) {

        /* set updated attributes from UI components to AppUser */
        setNewAttributes();

        /* disable editing text in all UI components*/
        enableDisableEntries();
        setSessionUserToDatabase(view);

        this.modifyButton.setVisibility(View.VISIBLE);
        this.doneButton.setVisibility(View.GONE);
        this.removeImageButton.setVisibility(View.GONE);
        this.changeImageButton.setVisibility(View.GONE);
    }

    /**
     * @param user sets the value of the current user to the session user object
     */
    private void setSessionUserToLocal(Pair<User, Boolean> user) {
        this.sessionUser = user;
        modifyButton.setVisibility(View.VISIBLE);

        /* set attributes of session user to the UI components */
        getAndSetCurrentAttributes();
    }

    /**
     * sets the updated user information to the firestore database and to the local database
     */
    private void setSessionUserToDatabase(View view) {
        mViewModel.updateUser(this.sessionUser.first);
        CompletableFuture<Void> saveRes = DatabaseSync.saveCurrentUserToLocalDB(this, database,
                localdb, mViewModel.getUser().getValue().first.getUserId());
        saveRes.exceptionally(e -> {
            Log.d("USER", "Failed to save user locally");
            return null;
        });
        mViewModel.getUpdateUserConfirmed().observe(this, this::informationUpdateResult);
    }

    private void imageUpdateResult(Boolean updateResult) {
        if (!updateResult) {
            UIUtils.makeSnakeForUserUpdateFailed(this.doneButton,
                    R.string.updateUserImageFailedInDB);
        }
        this.doneButton.setEnabled(true);
    }

    private void informationUpdateResult(Boolean updateResult) {
        if (updateResult) {
            /* update the view with new attributes */
            getAndSetCurrentAttributes();
        } else {
            UIUtils.makeSnakeForUserUpdateFailed(this.doneButton, R.string.updateUserFailedInDB);
        }
    }

    /**
     * enables and disables UI components when edit
     */
    private void enableDisableEntries() {
        this.nameEditText.setEnabled(!this.nameEditText.isEnabled());
        this.ageEditText.setEnabled(!this.ageEditText.isEnabled());
        this.genderSpinner.setEnabled(!this.genderSpinner.isEnabled());
        this.phoneNumberEditText.setEnabled(!this.phoneNumberEditText.isEnabled());
        /* email is never enabled since another process is required to edit it */
    }

    /**
     * sets the new attributes to the session User (local)
     */
    private void setNewAttributes() {
        String ageString =
                ((EditText) findViewById(R.id.age_UserProfile_editText)).getText().toString().trim();
        if (!ageString.equals("")) {
            this.sessionUser.first.setAge(Integer.parseInt(ageString));
        } else {
            this.sessionUser.first.setAge(0);
        }

        this.sessionUser.first.setName(((TextView) findViewById(R.id.name_UserProfile_editText)).getText().toString());
        this.sessionUser.first.setPhoneNumber(((EditText) findViewById(R.id.phoneNumber_UserProfile_editText)).getText().toString().trim());

        this.sessionUser.first.setGender(Gender.ALL.get(((Spinner) findViewById(R.id.gender_UserProfile_spinner)).getSelectedItemPosition()).name());

        /* LOOKS USELESS because of method naming: in case the user changed gender
           gender the gender is locally updated above. This method then updates the correct
           correct default user icon accordingly to the newly selected gender */
        if (this.sessionUser.first.hasDefaultProfileImage()) {
            this.sessionUser.first.setDefaultProfileImage();
        }
    }

    /**
     * sets the current session User attributes to the UI components
     */
    private void getAndSetCurrentAttributes() {

        User user = this.sessionUser.first;

        this.nameEditText.setText(user.getName());
        this.emailTextView.setText(user.getUserEmail());
        this.uniAccountClaimer.setText((user.hasUniversityEmail() ?
                getString(R.string.uniAccountClaimer) : getString(R.string.nonUniAccountClaimer)));
        if (user.getAge() != 0) {
            this.ageEditText.setText(String.valueOf(user.getAge()));
        }
        if (user.getPhoneNumber() != null) {
            this.phoneNumberEditText.setText(user.getPhoneNumber());
        }
        if (user.getGender() != null) {
            this.genderSpinner.setSelection(Gender.valueOf(user.getGender()).ordinal());
        }
        setPictureToImageComponent();
    }

    /**
     * sets the user profile picture (or default gender picture) to the ImageView component
     */
    private void setPictureToImageComponent() {
        boolean isLocal = this.sessionUser.second;
        if(isLocal) {
            Bitmap profilePic = BitmapFactory.decodeFile(this.sessionUser.first.getProfileImagePathAndName());
            Glide.with(this).load(profilePic).into(imageView);
        } else {
            database.accept(new GlideImageViewLoader(this, imageView,
                    this.sessionUser.first.getProfileImagePathAndName()));
        }



    }

}