com.concavenp.artistrymuse.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.concavenp.artistrymuse.MainActivity.java

Source

/*
 * ArtistryMuse is an application that allows artist to share projects
 * they have created along with the inspirations behind them for others to
 * discover and enjoy.
 * Copyright (C) 2017  David A. Todd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.concavenp.artistrymuse;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes;
import android.support.annotation.MainThread;
import android.support.annotation.StyleRes;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.TabLayout;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.NestedScrollView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.ViewFlipper;

import com.concavenp.artistrymuse.fragments.SearchFragment;
import com.concavenp.artistrymuse.fragments.adapter.ArtistryFragmentPagerAdapter;
import com.concavenp.artistrymuse.services.UserAuthenticationService;
import com.firebase.ui.auth.AuthUI;

import java.util.ArrayList;
import java.util.List;

/**
 * References:
 *
 * How to place the Floating Action Button exclusively on one fragment only in Tab layout
 *      - https://stackoverflow.com/questions/30926528/how-to-place-the-floating-action-button-exclusively-on-one-fragment-only-in-tab
 *
 */
public class MainActivity extends BaseAppCompatActivity implements
        UserAuthenticationService.OnAuthenticationListener, SharedPreferences.OnSharedPreferenceChangeListener {

    /**
     * The logging tag string to be associated with log data for this class
     */
    @SuppressWarnings("unused")
    private static final String TAG = MainActivity.class.getSimpleName();

    /**
     * URLs for Google specific authentication services explanations to the user
     */
    private static final String GOOGLE_TOS_URL = "https://www.google.com/policies/terms/";
    private static final String GOOGLE_PRIVACY_POLICY_URL = "https://www.google.com/policies/privacy/";

    /**
     * This service is used to translate the Firebase UID of the authenticated user to an app
     * specified ArtistryMuse UID.  The service listens for Firebase authentication events
     * and sets a SharedPreferences value accordingly.  SharedPreferences is where the app
     * specific UID will be stored for the duration of the the user's "logged in" experience.
     *
     * The rest of the app will use the app specific UID for Firebase database and storage
     * lookups.
     */
    private UserAuthenticationService mService;

    /**
     * Value used when referencing results from starting activities via FirebaseUI library.
     */
    private static final int RC_SIGN_IN = 100;

    /**
     * Flag used to indicate if our one time starting/binding of the service has been performed.
     */
    private boolean mBound = false;

    /**
     * The FAB for creating new projects.  This FAB will only be visible in the Gallery fragment.
     */
    private FloatingActionButton fabCreateProject;

    /**
     * Field used in determining how various UI elements are displayed within a Large or Not Large display
     */
    private boolean mIsLargeLayout;

    /**
     * The Shared Preferences key lookup value for identifying the last used tab position.
     */
    private static final String TAB_POSITION = "TAB_POSITION";

    /**
     * Default tab position
     */
    private static final int DEFAULT_TAB_POSITION = 0;

    /**
     * The position of the currently selected tab.  Initialized to the first tab.  This value will
     * be used in saving out the app's Preferences in order to preserve location of where the user
     * left off from either navigating to a different activity or restarting the app.
     */
    private int tabPosition = DEFAULT_TAB_POSITION;

    /**
     * The layout of the tabs.  Used here in a field for the same reason as the tabPosition field.
     */
    private TabLayout tabLayout;

    private ArtistryFragmentPagerAdapter mFragmentAdapter;

    private ViewFlipper mFlipper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        // If the application has not run before then initialize the preference settings with default values
        if (savedInstanceState == null) {

            // These are the "general" preferences (its all this app has)
            PreferenceManager.setDefaultValues(this, R.xml.pref_general, false);

        }

        // Force the scroll view to fill the area, dunno why this is not the default.
        NestedScrollView scrollView = findViewById(R.id.nest_scrollview);
        scrollView.setFillViewport(true);

        // Get the ViewPager and set it's PagerAdapter so that it can display items
        ViewPager viewPager = findViewById(R.id.viewpager);
        mFragmentAdapter = new ArtistryFragmentPagerAdapter(this, getSupportFragmentManager());
        viewPager.setAdapter(mFragmentAdapter);
        viewPager.setOffscreenPageLimit(mFragmentAdapter.getCount());
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                // Do nothing
            }

            @Override
            public void onPageSelected(int position) {

                // Save off the tab position
                tabPosition = position;

                // Perform animations (if needed)
                animateFab(position);

                // Close the keyboard if it is open
                View view = getCurrentFocus();
                if (view != null) {
                    InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
                }

            }

            @Override
            public void onPageScrollStateChanged(int state) {
                // Do nothing
            }
        });

        // Give the TabLayout the ViewPager
        tabLayout = findViewById(R.id.tabs);
        tabLayout.setupWithViewPager(viewPager);
        tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                animateFab(tab.getPosition());
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {
                // Do nothing
            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {
                // Do nothing
            }
        });

        // Setup the support for creating a menu (ActionBar functionality)
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Setup the FAB for creating a new user project (only visible in the gallery tab)
        fabCreateProject = findViewById(R.id.fabCreateProject);
        fabCreateProject.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                // Notify 'this' of the Create New Project selection
                onInteractionSelection(null, null, StorageDataType.PROJECTS, UserInteractionType.EDIT);

            }
        });

        // Logon button
        Button loginButton = findViewById(R.id.login_button);
        loginButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                onLoginInteraction();

            }
        });

        // Default will be hidden
        fabCreateProject.hide();

        // Save off the flipper for use in deciding which view to show
        mFlipper = findViewById(R.id.activity_main_ViewFlipper);

        // Set the flip view accordingly
        checkLoginFlipState();

        // Start the sign-in activity if nobody is logged in yet
        if (getUid().isEmpty()) {

            onLoginInteraction();

        }

    }

    /**
     * Controls the visibility of the FAB button(s) depending on the position of the displayed tab.
     *
     * @param position - The current tab position currently being displayed
     */
    private void animateFab(int position) {
        switch (position) {
        case 0:
            fabCreateProject.hide();
            break;
        case 1:
            fabCreateProject.hide();
            break;
        case 2:
            fabCreateProject.hide();
            break;
        case 3:
            fabCreateProject.show();
            break;
        default:
            fabCreateProject.hide();
            break;
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main_menu, menu);

        return true;

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == RC_SIGN_IN) {

            // Successfully signed in
            if (resultCode == RESULT_OK) {
                Log.d(TAG, "was able to log in");
            } else {
                Log.d(TAG, "was NOT able to log in");
            }

        }

    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        // The default result is that, no, we did not handle the given item
        boolean result;

        switch (item.getItemId()) {

        case R.id.action_profile: {

            // User chose to open the User Profile Editor
            onProfileInteraction();

            // We handled it
            result = true;

            break;

        }
        case R.id.action_settings: {

            Intent modifySettings = new Intent(MainActivity.this, SettingsActivity.class);

            // For now, give the activity some extra parameters that will tell it to use a specific
            // PreferenceFragment when starting up.  These lines can be removed when the user
            // preference settings gets more complex and require displaying preference headers to the user.
            modifySettings.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT,
                    SettingsActivity.GeneralPreferenceFragment.class.getName());
            modifySettings.putExtra(PreferenceActivity.EXTRA_NO_HEADERS, true);

            startActivity(modifySettings);

            // We handled it
            result = true;

            break;

        }
        case R.id.action_logoff: {

            // The user chose to log out
            AuthUI.getInstance().signOut(this);

            // We handled it
            result = true;

            break;

        }
        default: {

            // If we got here, the user's action was not recognized.
            // Invoke the superclass to handle it.
            result = super.onOptionsItemSelected(item);

            break;

        }

        }

        return result;

    }

    /**
     * Implementation for the interface that provides the ability for this activity to be
     * notified when the user needs to login into the Firebase service.  The action will be to
     * start a new login activity provided by the FirebaseUI library.
     */
    @Override
    public void onLoginInteraction() {

        // TODO: There is a bug within the FirebaseUI library for hitting the enter key when logging in via email:
        //
        // https://stackoverflow.com/questions/44741308/firebase-ui-auth-2-0-1-showing-multiple-welcome-back-password-prompt-screens
        //
        // Not very much I can do from my end, but this is only an issue in the emulator.
        //

        startActivityForResult(AuthUI.getInstance().createSignInIntentBuilder().setTheme(getSelectedTheme())
                .setLogo(getSelectedLogo()).setAvailableProviders(getSelectedProviders())
                .setTosUrl(getSelectedTosUrl()).setPrivacyPolicyUrl(getSelectedPrivacyPolicyUrl())
                .setIsSmartLockEnabled(false).setAllowNewEmailAccounts(true).build(), RC_SIGN_IN);

    }

    @MainThread
    private String getSelectedTosUrl() {
        return GOOGLE_TOS_URL;
    }

    @MainThread
    private String getSelectedPrivacyPolicyUrl() {
        return GOOGLE_PRIVACY_POLICY_URL;
    }

    @MainThread
    @StyleRes
    private int getSelectedTheme() {
        return R.style.AppTheme;
    }

    @MainThread
    @DrawableRes
    private int getSelectedLogo() {
        return R.drawable.ic_muse_logo_2_vector;
    }

    @MainThread
    private List<AuthUI.IdpConfig> getSelectedProviders() {

        List<AuthUI.IdpConfig> selectedProviders = new ArrayList<>();

        // These will be the available providers
        selectedProviders.add(new AuthUI.IdpConfig.Builder(AuthUI.GOOGLE_PROVIDER).build());
        selectedProviders.add(new AuthUI.IdpConfig.Builder(AuthUI.EMAIL_PROVIDER).build());

        return selectedProviders;

    }

    /**
     * Implementation for the interface that provides the ability for this activity to be
     * notified when the user needs to fill out profile information about themselves.  As in a
     * new user situation.
     */
    @Override
    public void onProfileInteraction() {

        Intent intent = new Intent(this, ProfileActivity.class);
        startActivity(intent);

    }

    @Override
    protected void onStart() {

        super.onStart();

        // Bind to LocalService
        Intent intent = new Intent(this, UserAuthenticationService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

        // Read in the current tab location from the Shared Preferences and select that tab
        SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
        int position = sharedPref.getInt(TAB_POSITION, DEFAULT_TAB_POSITION);
        TabLayout.Tab tab = tabLayout.getTabAt(position);

        // Verify there is a tab to work with
        if (tab != null) {
            tab.select();
        }

    }

    @Override
    protected void onStop() {

        super.onStop();

        // Unbind from the service
        if (mBound) {

            // There should be nobody around to listen now
            mService.clearRegisteredAuthenticationListener();

            unbindService(mConnection);

            mBound = false;

        }

        // Save the current tab location to the Shared Preferences
        SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPref.edit();
        editor.putInt(TAB_POSITION, tabPosition);
        editor.apply();

    }

    @Override
    protected void onResume() {

        super.onResume();

        // Register as a listener to SharedPreference ArtistryMuseUID changes
        mSharedPreferences.registerOnSharedPreferenceChangeListener(this);

        // Set the flip view accordingly
        checkLoginFlipState();

    }

    @Override
    protected void onPause() {

        super.onPause();

        // Unregister as a listener to SharedPreference ArtistryMuseUID changes
        mSharedPreferences.unregisterOnSharedPreferenceChangeListener(this);

    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {

        // Just for the UID changes
        if (key.equals(getResources().getString(R.string.application_uid_key))) {

            // Set the flip view accordingly
            checkLoginFlipState();

        }

    }

    /**
     * Helper method to set the shown view according to the User login state reflected by the
     * SharedPreferences value of the User's UID.  If there is a UID set then show that user's
     * data, otherwise flip to a view that tells the user to login.
     */
    private void checkLoginFlipState() {

        if (getUid().isEmpty()) {

            mFlipper.setDisplayedChild(mFlipper.indexOfChild(mFlipper.findViewById(R.id.login_frame)));

        } else {

            mFlipper.setDisplayedChild(mFlipper.indexOfChild(mFlipper.findViewById(R.id.coordinatorLayout)));

        }

    }

    /**
     * Intercept the signal of when keyboard presses are released.  We are specifically checking for
     * when the Enter key is hit.  If this is the case then we want to check if we are in the
     * Search fragment and signal it of the Enter press if it is.
     */
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {

        switch (keyCode) {

        case KeyEvent.KEYCODE_ENTER: {

            Class result = mFragmentAdapter.getClassFromPosition(tabPosition);

            if (result == SearchFragment.class) {

                SearchFragment searchFragment = (SearchFragment) mFragmentAdapter.getItem(tabPosition);

                searchFragment.onKeyUp();

            }

            return true;

        }
        default: {

            return super.onKeyUp(keyCode, event);

        }

        }

    }

    /**
     * Defines callbacks for service binding, passed to bindService()
     */
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {

            // We've bound to LocalService, cast the IBinder and get LocalService instance
            UserAuthenticationService.LocalBinder binder = (UserAuthenticationService.LocalBinder) service;
            mService = binder.getService();

            // Register this class as a listener for login and profile events
            mService.registerAuthenticationListener(MainActivity.this);

            // Our one time bounding has occurred
            mBound = true;

        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {

            // There should be nobody around to listen now
            mService.clearRegisteredAuthenticationListener();

            mBound = false;

        }

    };

}