it.polimi.spf.app.fragments.profile.ProfileFragment.java Source code

Java tutorial

Introduction

Here is the source code for it.polimi.spf.app.fragments.profile.ProfileFragment.java

Source

/* 
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 * 
 * This file is part of SPF.
 * 
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free 
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package it.polimi.spf.app.fragments.profile;

import java.util.List;

import com.astuetz.viewpager.extensions.PagerSlidingTabStrip;

import it.polimi.spf.app.R;
import it.polimi.spf.app.fragments.contacts.ContactConfirmDialogView;
import it.polimi.spf.framework.SPF;
import it.polimi.spf.framework.profile.SPFPersona;
import it.polimi.spf.framework.proximity.SPFRemoteInstance;
import it.polimi.spf.app.view.CircleImageView;
import it.polimi.spf.shared.model.ProfileField;
import it.polimi.spf.shared.model.ProfileFieldContainer;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.app.LoaderManager;
import android.content.AsyncTaskLoader;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.Loader;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.Toast;

public class ProfileFragment extends Fragment
        implements LoaderManager.LoaderCallbacks<ProfileFieldContainer>, OnItemSelectedListener, OnClickListener {

    /**
     * Possible visualization modes of fields values.
     * 
     * @author darioarchetti
     */
    public static enum Mode {
        /**
         * Shows the profile of the local user
         */
        SELF,

        /**
         * Shows the profile of a remote user
         */
        REMOTE,

        /**
         * Shows the profile of the local user and allows modifications
         */
        EDIT;
    }

    /**
     * Creates a new instance of {@link ProfileFragment} to show the profile of
     * a remote user. The fragment will allow only to view the fields and not to
     * modify them.
     * 
     * @param personIdentifer
     *            - the identifier of the person whose profile to show
     * @return an instance of ProfileFragment
     */
    public static ProfileFragment createRemoteProfileFragment(String personIdentifer) {
        Bundle b = new Bundle();
        b.putInt(EXTRA_VIEW_MODE, Mode.REMOTE.ordinal());
        b.putString(EXTRA_PERSON_IDENTIFIER, personIdentifer);
        ProfileFragment fragment = new ProfileFragment();
        fragment.setArguments(b);
        return fragment;
    }

    /**
     * Creates a new instance of ProfileFragment to show the local profile. The
     * fragment may also allow to modify the values depending on the given
     * {@link Mode}.
     * 
     * @param mode
     *            - the visualization {@link Mode}.
     * @return an instance of ProfileFragment
     */
    public static ProfileFragment createViewSelfProfileFragment() {
        Bundle b = new Bundle();
        b.putInt(EXTRA_VIEW_MODE, Mode.SELF.ordinal());
        ProfileFragment fragment = new ProfileFragment();
        fragment.setArguments(b);
        return fragment;
    }

    public static ProfileFragment createEditSelfProfileFragment(SPFPersona persona) {
        Bundle b = new Bundle();
        b.putInt(EXTRA_VIEW_MODE, Mode.EDIT.ordinal());
        b.putParcelable(EXTRA_CURRENT_PERSONA, persona);
        ProfileFragment fragment = new ProfileFragment();
        fragment.setArguments(b);
        return fragment;
    }

    private static final String EXTRA_PERSON_IDENTIFIER = "personIdentifier";
    private static final String EXTRA_PROFILE_CONTAINER = "profileContainer";
    private static final String EXTRA_CURRENT_PERSONA = "persona";
    private static final String EXTRA_VIEW_MODE = "viewMode";

    private static final int LOAD_PROFILE_LOADER_ID = 0;
    private static final int SAVE_PROFILE_LOADER_ID = 1;

    private static final int ACTIVITY_EDIT_PROFILE_CODE = 0;
    private static final int ACTIVITY_EDIT_PROFILE_PICTURE_CODE = 1;
    private static final String PHOTO_WIDTH = "50dp";
    private static final String PHOTO_HEIGHT = "50dp";
    private static final String TAG = "ProfileFragment";

    private String mPersonIdentifier;
    private SPFPersona mCurrentPersona;
    private Mode mMode;
    private ProfileFieldContainer mContainer;

    private ProfilePagerAdapter mPagerAdapter;
    private ViewPager mViewPager;
    private ProfileFieldViewFactory mFactory;
    private boolean mModifiedAtLeastOnce = false;

    // Lifecycle management methods

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.content_fragment_profile, container, false);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        if (savedInstanceState == null) {
            mMode = Mode.values()[getArguments().getInt(EXTRA_VIEW_MODE)];
            switch (mMode) {
            case SELF:
                String callerApp = getActivity().getCallingPackage();
                if (callerApp == null) {
                    mCurrentPersona = SPFPersona.DEFAULT;
                } else {
                    mCurrentPersona = SPF.get().getSecurityMonitor().getPersonaOf(callerApp);
                }
                break;
            case REMOTE:
                mPersonIdentifier = getArguments().getString(EXTRA_PERSON_IDENTIFIER);
                break;
            case EDIT:
                mCurrentPersona = getArguments().getParcelable(EXTRA_CURRENT_PERSONA);
            }

            // Initialize the loader of profile information
            startLoader(LOAD_PROFILE_LOADER_ID);
        } else {
            mPersonIdentifier = savedInstanceState.getString(EXTRA_PERSON_IDENTIFIER);
            mCurrentPersona = savedInstanceState.getParcelable(EXTRA_CURRENT_PERSONA);
            mMode = Mode.values()[savedInstanceState.getInt(EXTRA_VIEW_MODE)];
            mContainer = savedInstanceState.getParcelable(EXTRA_PROFILE_CONTAINER);

            onProfileDataAvailable();
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(EXTRA_PERSON_IDENTIFIER, mPersonIdentifier);
        outState.putParcelable(EXTRA_CURRENT_PERSONA, mCurrentPersona);
        outState.putInt(EXTRA_VIEW_MODE, mMode.ordinal());
        outState.putParcelable(EXTRA_PROFILE_CONTAINER, mContainer);
    }

    /*
     * LOADERS - Used to load and save profile data.
     */
    @Override
    public Loader<ProfileFieldContainer> onCreateLoader(int id, Bundle args) {
        switch (id) {
        case LOAD_PROFILE_LOADER_ID:
            return new AsyncTaskLoader<ProfileFieldContainer>(getActivity()) {

                @Override
                public ProfileFieldContainer loadInBackground() {
                    if (mMode == Mode.SELF || mMode == Mode.EDIT) {
                        return SPF.get().getProfileManager().getProfileFieldBulk(mCurrentPersona,
                                ProfilePagerAdapter.DEFAULT_FIELDS);
                    } else {
                        SPFRemoteInstance instance = SPF.get().getPeopleManager().getPerson(mPersonIdentifier);
                        if (instance == null) {
                            throw new IllegalStateException(
                                    "Person " + mPersonIdentifier + " not found in proximity");
                        } else {
                            String app = getActivity().getCallingPackage();
                            app = app == null ? "it.polimi.spf.app" : app;
                            return instance.getProfileBulk(
                                    ProfileField.toIdentifierList(ProfilePagerAdapter.DEFAULT_FIELDS), app);
                        }
                    }
                }
            };

        case SAVE_PROFILE_LOADER_ID:
            if (mMode != Mode.EDIT) {
                Log.e(TAG, "SAVE_PROFILE_LOADER initialized in mode " + mMode);
            }

            return new AsyncTaskLoader<ProfileFieldContainer>(getActivity()) {

                @Override
                public ProfileFieldContainer loadInBackground() {
                    SPF.get().getProfileManager().setProfileFieldBulk(mContainer, mCurrentPersona);
                    return null;
                }
            };

        default:
            throw new IllegalArgumentException("No loader for id " + id);
        }
    }

    @Override
    public void onLoadFinished(Loader<ProfileFieldContainer> loader, ProfileFieldContainer data) {
        switch (loader.getId()) {
        case LOAD_PROFILE_LOADER_ID:
            mContainer = data;
            onProfileDataAvailable();
            break;
        case SAVE_PROFILE_LOADER_ID:
            mContainer.clearModified();
            mModifiedAtLeastOnce = true;
            getActivity().finish();
            break;
        }
    }

    @Override
    public void onLoaderReset(Loader<ProfileFieldContainer> loader) {
        // Do nothing
    }

    // Called when the profile data is available, thus we can set up the view
    private void onProfileDataAvailable() {
        Log.d(TAG, "onProfileDataAvailable");
        mFactory = new ProfileFieldViewFactory(getActivity(), mMode, mCurrentPersona, mContainer);

        // Populate field list
        mPagerAdapter = new ProfilePagerAdapter(getActivity(), getChildFragmentManager(), mMode);
        mViewPager = (ViewPager) getView().findViewById(R.id.profileedit_pager);

        mViewPager.setAdapter(mPagerAdapter);
        mViewPager.setOffscreenPageLimit(2);

        PagerSlidingTabStrip tabs = (PagerSlidingTabStrip) getView().findViewById(R.id.profileedit_tabs);
        tabs.setViewPager(mViewPager);

        showPicture(mContainer.getFieldValue(ProfileField.PHOTO));

        // Refresh field fragments
        mPagerAdapter.onRefresh();
    }

    private void showPicture(Bitmap photo) {
        // Show picture
        CircleImageView profilePicView = (CircleImageView) getView().findViewById(R.id.profile_picture);
        if (mMode == Mode.EDIT) {
            profilePicView.setOnClickListener(this);
        }

        profilePicView.setBackground(photo);
        profilePicView.invalidate();
    }

    // Methods to be called from child ProfileFieldsFragment to obtain views and
    // to notify of
    // changes in values of circles

    /**
     * Creates a view for the given profile field. Depending on the {@link Mode}
     * of visualization, the view may allow the modification of the value. This
     * method will not attach the view to the provided {@link ViewGroup}
     * 
     * @param field
     *            - the field for which to create the view.
     * @param container
     *            - the container to which the view will be attached, needed by
     *            {@link LayoutInflater#inflate(int, ViewGroup, boolean)} to
     *            evaluate layout params.
     * @return
     */
    public <E> View createViewFor(ProfileField<E> field, ViewGroup container) {
        switch (mMode) {
        case SELF:
        case REMOTE:
            return mFactory.createViewForField(field, container, null);
        case EDIT:
            return mFactory.createViewForField(field, container,
                    new ProfileFieldViewFactory.FieldValueListener<E>() {

                        @Override
                        public void onFieldValueChanged(ProfileField<E> field, E value) {
                            mContainer.setFieldValue(field, value);
                        }

                        @Override
                        public void onInvalidFieldValue(ProfileField<E> field, String fieldFriendlyName) {
                            Toast.makeText(getActivity(), "Invalid value for field " + fieldFriendlyName,
                                    Toast.LENGTH_SHORT).show();
                        }

                        @Override
                        public void onCircleAdded(ProfileField<E> field, String circle) {
                            Log.d(TAG, "Circle " + circle + " added to field " + field + " of persona "
                                    + mCurrentPersona);
                            SPF.get().getProfileManager().addGroupToField(field, circle, mCurrentPersona);
                        }

                        @Override
                        public void onCircleRemoved(ProfileField<E> field, String circle) {
                            Log.d(TAG, "Circle " + circle + " removed from field " + field + " of persona "
                                    + mCurrentPersona);
                            SPF.get().getProfileManager().removeGroupFromField(field, circle, mCurrentPersona);
                        }
                    });
        default:
            return null;
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
        case ACTIVITY_EDIT_PROFILE_CODE:
            // Profile may have changed, reload it
            if (resultCode == Activity.RESULT_CANCELED) {
                Log.d(TAG, "Edit finished but no data was modified");
            }

            onProfileDataSaved();
            startLoader(LOAD_PROFILE_LOADER_ID);
            break;
        case ACTIVITY_EDIT_PROFILE_PICTURE_CODE:
            if (resultCode != Activity.RESULT_OK) {
                return;
            }

            if (data != null && data.getExtras() != null) {
                Bitmap photo = data.getExtras().getParcelable("data");
                mContainer.setFieldValue(ProfileField.PHOTO, photo);
                showPicture(photo);
            }
        }

        super.onActivityResult(requestCode, resultCode, data);
    }

    /*
     * MENU
     */

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        switch (mMode) {
        case SELF:
            inflater.inflate(R.menu.menu_view_self_profile, menu);
            break;
        case REMOTE:
            if (SPF.get().getSecurityMonitor().getPersonRegistry().lookup(mPersonIdentifier) == null) {
                inflater.inflate(R.menu.menu_view_other_profile, menu);
            }
            break;
        case EDIT:
            inflater.inflate(R.menu.menu_edit_profile, menu);
            break;
        }
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
        MenuItem personaSelector = menu.findItem(R.id.profileview_persona_selector);
        if (personaSelector == null) {
            return;
        }

        Spinner spinner = (Spinner) personaSelector.getActionView().findViewById(R.id.profileview_persona_spinner);
        if (spinner == null) {
            return;
        }

        List<SPFPersona> personas = SPF.get().getProfileManager().getAvailablePersonas();
        spinner.setAdapter(new ArrayAdapter<SPFPersona>(getActivity().getActionBar().getThemedContext(),
                android.R.layout.simple_list_item_1, personas));
        spinner.setSelection(personas.indexOf(mCurrentPersona), false);
        spinner.setOnItemSelectedListener(this);

    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.profileview_edit:
            Intent i = new Intent(getActivity(), ProfileEditActivity.class);
            i.putExtra(ProfileEditActivity.EXTRA_PERSONA, mCurrentPersona);
            startActivityForResult(i, ACTIVITY_EDIT_PROFILE_CODE);
            return true;
        case R.id.profileview_send_contact_request:
            final String displayName = mContainer.getFieldValue(ProfileField.DISPLAY_NAME);
            final Bitmap picture = mContainer.getFieldValue(ProfileField.PHOTO);

            final ContactConfirmDialogView view = new ContactConfirmDialogView(getActivity(), displayName, picture);
            new AlertDialog.Builder(getActivity()).setTitle("Send contact request?").setView(view)
                    .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            SPF.get().getSecurityMonitor().getPersonRegistry().sendContactRequestTo(
                                    mPersonIdentifier, view.getPassphrase(), displayName, picture,
                                    view.getSelectedCircles());
                        }
                    }).setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    }).show();

            return true;
        case R.id.profileedit_save:
            if (mContainer.isModified()) {
                startLoader(SAVE_PROFILE_LOADER_ID);
            } else {
                onSaveNotNecessary();
            }
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    /*
     * Click on the profile picture in edit mode starts an activity to change
     * the profile picture
     */
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(Intent.ACTION_PICK,
                android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
        intent.setType("image/*");
        intent.putExtra("crop", "true");
        intent.putExtra("scale", true);
        intent.putExtra("outputX", PHOTO_WIDTH);
        intent.putExtra("outputY", PHOTO_HEIGHT);
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        intent.putExtra("return-data", true);
        startActivityForResult(intent, ACTIVITY_EDIT_PROFILE_PICTURE_CODE);
    }

    /*
     * ItemSelectListener for Persona spinner in actionbar
     */
    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        SPFPersona persona = (SPFPersona) parent.getItemAtPosition(position);
        if (mCurrentPersona.equals(persona)) {
            return;
        }

        mCurrentPersona = persona;
        startLoader(LOAD_PROFILE_LOADER_ID);
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
        // Do nothing
    }

    private void startLoader(int id) {
        getLoaderManager().destroyLoader(id);
        getLoaderManager().initLoader(id, null, this).forceLoad();
    }

    private void onProfileDataSaved() {
        Toast.makeText(getActivity(), "Profile data saved", Toast.LENGTH_SHORT).show();
    }

    private void onSaveNotNecessary() {
        Toast.makeText(getActivity(), "No field modified", Toast.LENGTH_SHORT).show();
    }

    public boolean isContainerModified() {
        return mContainer.isModified();
    }

    public boolean isContainerModifiedAtLeastOnce() {
        return mModifiedAtLeastOnce;
    }
}