cz.maresmar.sfm.view.guide.WelcomeActivity.java Source code

Java tutorial

Introduction

Here is the source code for cz.maresmar.sfm.view.guide.WelcomeActivity.java

Source

/*
 * SmartFoodMenu - Android application for canteens extendable with plugins
 *
 * Copyright  2016-2018  Martin Mare <mmrmartin[at]gmail[dot]com>
 *
 * 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 <https://www.gnu.org/licenses/>.
 */

package cz.maresmar.sfm.view.guide;

import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.annotation.UiThread;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Hashtable;

import cz.maresmar.sfm.R;
import cz.maresmar.sfm.app.SettingsContract;
import cz.maresmar.sfm.app.SfmApp;
import cz.maresmar.sfm.plugin.BroadcastContract;
import cz.maresmar.sfm.provider.ProviderContract;
import cz.maresmar.sfm.service.plugin.PortalTestHandler;
import cz.maresmar.sfm.service.plugin.sync.SyncHandler;
import cz.maresmar.sfm.utils.MenuUtils;
import cz.maresmar.sfm.view.DataForm;
import cz.maresmar.sfm.view.MainActivity;
import cz.maresmar.sfm.view.credential.CredentialDetailFragment;
import cz.maresmar.sfm.view.help.HelpFragment;
import cz.maresmar.sfm.view.portal.PortalDetailFragment;
import cz.maresmar.sfm.view.portal.PortalListFragment;
import cz.maresmar.sfm.view.user.UserDetailFragment;
import timber.log.Timber;

/**
 * Welcome guide activity
 * <p>
 * The activity shows when user opens app for the first time. It helps him add new user, select
 * portal and add credentials
 * </p>
 */
public class WelcomeActivity extends AppCompatActivity implements PortalListFragment.OnPortalSelectedListener,
        DataForm.DataValidityListener, PortalTestHandler.PortalTestResultListener {

    // Fragments IDs
    static private final int WELCOME_FRAGMENT_ID = 0;
    static private final int USER_FRAGMENT_ID = 1;
    static private final int PORTALS_LIST_FRAGMENT_ID = 2;
    static private final int PORTAL_FRAGMENT_ID = 3;
    static private final int CREDENTIALS_FRAGMENT_ID = 4;
    static private final int HELP_FRAGMENT_ID = 5;

    @IntDef(value = { WELCOME_FRAGMENT_ID, USER_FRAGMENT_ID, PORTALS_LIST_FRAGMENT_ID, PORTAL_FRAGMENT_ID,
            CREDENTIALS_FRAGMENT_ID, HELP_FRAGMENT_ID })
    @Retention(RetentionPolicy.SOURCE)
    private @interface FragmentId {
    }

    // Save instance state constants
    private static final String PAGE_ID = "page_id";
    private static final String MAX_FRAGMENT_ID = "max_fragment_id";
    private static final String ADVANCED_SETTINGS = "advanced_settings";
    private static final String USER_URI = "user_uri";
    private static final String VALIDATED_CREDENTIAL_ID = "validatedCredentialId";
    private static final String REFRESHING = "refreshing";

    private final BroadcastReceiver mSyncReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            long credentialId = intent.getExtras().getLong(BroadcastContract.EXTRA_CREDENTIAL_ID, -1);
            int worstResult = intent.getExtras().getInt(BroadcastContract.EXTRA_WORST_RESULT);
            onCredentialValidationResult(credentialId, worstResult);
        }
    };

    private final BroadcastReceiver mPortalTestReceiver = new PortalTestHandler.PortalTestResultReceiver(this);

    // UI elements
    private ViewPager mViewPager;
    private FloatingActionButton mFab;
    private MenuItem mAdvSettingsMenuItem;
    private Toast mActiveToast;

    // Internal state
    private boolean mAdvSettings = false;
    @FragmentId
    private int mMaxFragmentId = WELCOME_FRAGMENT_ID;
    private Uri mUserUri;
    private long mValidatedCredentialId = -1;
    private boolean mOnEndDiscardData = true;

    // Data sources
    /**
     * The {@link android.support.v4.view.PagerAdapter} that will provide
     * fragments for each of the sections. We use a
     * {@link FragmentPagerAdapter} derivative, which will keep every
     * loaded fragment in memory. If this becomes too memory intensive, it
     * may be best to switch to a
     * {@link android.support.v4.app.FragmentStatePagerAdapter}.
     */
    private PagerAdapter mPagerAdapter;
    private SwipeRefreshLayout mSwipeRefreshLayout;

    // -------------------------------------------------------------------------------------------
    // Activity lifecycle
    // -------------------------------------------------------------------------------------------

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setTheme(R.style.AppTheme_NoActionBar);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_welcome);

        // Toolbar
        Toolbar toolbar = findViewById(R.id.toolbar);
        toolbar.setNavigationIcon(R.drawable.ic_app);
        setSupportActionBar(toolbar);

        mSwipeRefreshLayout = findViewById(R.id.swipeRefreshLayout);
        mSwipeRefreshLayout.setColorSchemeColors(getResources().getIntArray(R.array.swipeRefreshColors));
        mSwipeRefreshLayout.setEnabled(false);

        // Create the adapter that will return a fragment for each of the three
        // primary sections of the activity.
        mPagerAdapter = new PagerAdapter(getSupportFragmentManager());

        // Set up the ViewPager with the sections adapter.
        mViewPager = findViewById(R.id.container);
        mViewPager.setAdapter(mPagerAdapter);
        // Watch for pages without FAB button
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            }

            @Override
            public void onPageSelected(int position) {
                // Limit last page in into
                if (position > mMaxFragmentId) {
                    position = mMaxFragmentId;
                    mViewPager.setCurrentItem(position, true);
                    if (position != PORTALS_LIST_FRAGMENT_ID) {
                        tryToMoveToNextPage();
                    }
                }

                // Fab button
                mFab.setVisibility(position == PORTALS_LIST_FRAGMENT_ID ? View.GONE : View.VISIBLE);

                // Set page title
                toolbar.setTitle(mPagerAdapter.getPageTitle(position));
            }

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

        // Floating action button
        mFab = findViewById(R.id.main_discard_fab);
        mFab.setOnClickListener(view -> tryToMoveToNextPage());

        registerReceiver(mPortalTestReceiver, new IntentFilter(PortalTestHandler.BROADCAST_PORTAL_TEST_RESULT));
        registerReceiver(mSyncReceiver, new IntentFilter(BroadcastContract.BROADCAST_PLUGIN_SYNC_RESULT));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.toolbar_welcome, menu);

        mAdvSettingsMenuItem = menu.findItem(R.id.advanced_check_box);
        mAdvSettingsMenuItem.setChecked(mAdvSettings);
        return true;
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Preload next fragments after some time
        new Handler().postDelayed(() -> mViewPager.setOffscreenPageLimit(3), 1000);
    }

    @Override
    protected void onStop() {
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        unregisterReceiver(mPortalTestReceiver);
        unregisterReceiver(mSyncReceiver);

        if (mOnEndDiscardData) {
            for (int i = 0; i < mPagerAdapter.getCount(); i++) {
                DataForm form = (DataForm) mPagerAdapter.getFragment(i);
                if (form == null)
                    break;

                form.discardTempData(this);
            }
        }
    }

    // -------------------------------------------------------------------------------------------
    // UI save and restore
    // -------------------------------------------------------------------------------------------

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(MAX_FRAGMENT_ID, mMaxFragmentId);
        outState.putInt(PAGE_ID, mViewPager.getCurrentItem());
        outState.putBoolean(ADVANCED_SETTINGS, mAdvSettings);
        outState.putParcelable(USER_URI, mUserUri);
        outState.putLong(VALIDATED_CREDENTIAL_ID, mValidatedCredentialId);
        outState.putBoolean(REFRESHING, mSwipeRefreshLayout.isRefreshing());
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        //noinspection WrongConstant
        mMaxFragmentId = savedInstanceState.getInt(MAX_FRAGMENT_ID, WELCOME_FRAGMENT_ID);
        mViewPager.setCurrentItem(savedInstanceState.getInt(PAGE_ID, WELCOME_FRAGMENT_ID), false);
        mAdvSettings = savedInstanceState.getBoolean(ADVANCED_SETTINGS, false);
        mUserUri = savedInstanceState.getParcelable(USER_URI);
        mValidatedCredentialId = savedInstanceState.getLong(VALIDATED_CREDENTIAL_ID, -1);
        mSwipeRefreshLayout.setRefreshing(savedInstanceState.getBoolean(REFRESHING));
    }

    // -------------------------------------------------------------------------------------------
    // UI events
    // -------------------------------------------------------------------------------------------

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        switch (item.getItemId()) {
        case R.id.action_about:
            SfmApp.startAboutActivity(this);
            return true;
        case R.id.action_feedback:
            SfmApp app = (SfmApp) getApplication();
            app.sendFeedback(this);
            return true;
        case R.id.advanced_check_box:
            // Android doesn't handle checked <-> unchecked behavior automatically
            // in popup menu
            mAdvSettings = !mAdvSettings;
            item.setChecked(mAdvSettings);
            // Make it survive inflate from fragments
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    /**
     * Done key on software keyboard and back key behaviour
     */
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        switch (keyCode) {
        case KeyEvent.KEYCODE_ENTER:
            // Done pressed
            tryToMoveToNextPage();
            return true;
        case KeyEvent.KEYCODE_BACK:
            if (mViewPager.getCurrentItem() != 0) {
                //Return to previous fragment
                mViewPager.setCurrentItem(mViewPager.getCurrentItem() - 1, true);
                return true;
            }
        default:
            return super.onKeyUp(keyCode, event);
        }
    }

    @UiThread
    private void tryToMoveToNextPage() {
        Timber.i("FAB clicked on page %d", mViewPager.getCurrentItem());

        if (mViewPager.getCurrentItem() == HELP_FRAGMENT_ID) {
            startMainActivity();
            return;
        }

        DataForm formFragment = (DataForm) mPagerAdapter.getFragment(mViewPager.getCurrentItem());
        if (formFragment == null) {
            // Skipping move forward when doing onRestoreInstanceState
            return;
        }
        if (formFragment.hasValidData()) {
            switch (mViewPager.getCurrentItem()) {
            case WELCOME_FRAGMENT_ID:
                moveToNextPage();
                break;
            case USER_FRAGMENT_ID: {
                mUserUri = formFragment.saveData();
                moveToNextPage();
                break;
            }
            case PORTAL_FRAGMENT_ID: {
                PortalDetailFragment portalDetailFragment = (PortalDetailFragment) formFragment;
                ((PortalDetailFragment) formFragment).saveAndTestData();
                mActiveToast = Toast.makeText(this, R.string.portal_checking_portal_data, Toast.LENGTH_LONG);
                mActiveToast.show();
                mSwipeRefreshLayout.setRefreshing(true);
                break;
            }
            case CREDENTIALS_FRAGMENT_ID: {
                CredentialDetailFragment credentialDetailFragment = (CredentialDetailFragment) formFragment;
                Uri credentialUri = credentialDetailFragment.saveData();
                mValidatedCredentialId = ContentUris.parseId(credentialUri);
                // Start plugin
                SyncHandler.startFullSync(this);
                mActiveToast = Toast.makeText(getBaseContext(), R.string.credential_detail_checking_credential_data,
                        Toast.LENGTH_LONG);
                mActiveToast.show();
                mSwipeRefreshLayout.setRefreshing(true);
                break;
            }
            default:
                throw new UnsupportedOperationException("Unsupported page " + mViewPager.getCurrentItem());

            }
        }
    }

    private void moveToNextPage() {
        if (mMaxFragmentId == mViewPager.getCurrentItem())
            //noinspection WrongConstant
            mMaxFragmentId = mViewPager.getCurrentItem() + 1;
        mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1, true);
    }

    @UiThread
    private void startMainActivity() {
        Intent intent = new Intent(this, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);

        // Plan auto sync in the background
        SyncHandler.planFullSync(this, Integer.parseInt(SettingsContract.SYNC_FREQUENCY_DEFAULT),
                SettingsContract.SYNC_UNMETERED_ONLY_DEFAULT, SettingsContract.SYNC_CHARGING_ONLY_DEFAULT);
    }

    // -------------------------------------------------------------------------------------------
    // Specific fragment events
    // -------------------------------------------------------------------------------------------

    @Override
    public void onPortalSelected(@Nullable Uri portalUri) {
        // Create new data
        if (portalUri == null) {
            mAdvSettings = true;
            mAdvSettingsMenuItem.setCheckable(true);
        }

        // Updates UI with selected portal
        PortalDetailFragment portalDetailFragment = (PortalDetailFragment) mPagerAdapter
                .getFragment(PORTAL_FRAGMENT_ID);
        portalDetailFragment.reset(portalUri);

        // Show correct fragment
        if (mAdvSettings) {
            mMaxFragmentId = PORTAL_FRAGMENT_ID;
            mViewPager.setCurrentItem(PORTAL_FRAGMENT_ID, true);
        } else {
            // Update credentials data according to selection
            CredentialDetailFragment credentialsFragment = (CredentialDetailFragment) mPagerAdapter
                    .getFragment(CREDENTIALS_FRAGMENT_ID);
            credentialsFragment.reset(mUserUri, portalUri);
            // Show Credentials fragment
            mMaxFragmentId = CREDENTIALS_FRAGMENT_ID;
            mViewPager.setCurrentItem(CREDENTIALS_FRAGMENT_ID, true);
        }
    }

    @Override
    public void onDataValidityChanged(@NonNull Fragment source, boolean validState) {
        if (validState) {
            if (mMaxFragmentId == mViewPager.getCurrentItem())
                //noinspection WrongConstant
                mMaxFragmentId = mViewPager.getCurrentItem() + 1;
        } else {
            if (source instanceof WelcomeFragment) {
                mMaxFragmentId = WELCOME_FRAGMENT_ID;
            } else if (source instanceof UserDetailFragment) {
                mMaxFragmentId = USER_FRAGMENT_ID;
            } else if (source instanceof PortalDetailFragment) {
                mMaxFragmentId = PORTAL_FRAGMENT_ID;
            }
        }
    }

    @Override
    public void onPortalTestResult(long portalId, @BroadcastContract.TestResult int result,
            @Nullable String errorMsg) {
        Timber.i("Received portal %d test result %d (msg: %s)", portalId, result, errorMsg);
        // Hide message about sync
        mActiveToast.cancel();
        mSwipeRefreshLayout.setRefreshing(false);

        switch (result) {
        case BroadcastContract.TEST_RESULT_OK: {
            Uri portalUri = ContentUris.withAppendedId(ProviderContract.Portal.getUri(), portalId);

            // Update credential fragment with new data
            CredentialDetailFragment credentialsFragment = (CredentialDetailFragment) mPagerAdapter
                    .getFragment(CREDENTIALS_FRAGMENT_ID);
            credentialsFragment.reset(mUserUri, portalUri);
            moveToNextPage();
            break;
        }
        case BroadcastContract.TEST_RESULT_INVALID_DATA:
            // Show error message
            if (errorMsg == null) {
                errorMsg = getString(R.string.extra_invalid_input_error);
            }
            Snackbar.make(findViewById(android.R.id.content), errorMsg, Snackbar.LENGTH_LONG)
                    .setAction(android.R.string.ok, view -> {
                        // Only dismiss message
                    }).show();
            break;
        default:
            throw new UnsupportedOperationException("Unexpected test result" + result);
        }
    }

    private void onCredentialValidationResult(long credentialId, @BroadcastContract.SyncResult int worstResult) {
        Timber.i("Received credential %d (expected %d) test result %d", credentialId, mValidatedCredentialId,
                worstResult);
        if (credentialId != mValidatedCredentialId) {
            return;
        }
        if (mActiveToast != null) {
            mActiveToast.cancel();
        }
        mSwipeRefreshLayout.setRefreshing(false);

        if (worstResult == BroadcastContract.RESULT_OK) {
            mOnEndDiscardData = false;
            PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean(SettingsContract.FIRST_RUN, false)
                    .apply();
            moveToNextPage();
        } else {
            @StringRes
            int errMsg = MenuUtils.getSyncErrorMessage(worstResult);

            Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), errMsg, Snackbar.LENGTH_LONG);

            if (errMsg == BroadcastContract.RESULT_UNKNOWN_PORTAL_FORMAT) {
                // Give user option to send logs
                snackbar.setAction(R.string.action_feedback_send, view -> {
                    SfmApp app = (SfmApp) getApplication();
                    app.sendFeedback(this);
                });
            } else {
                // Only dismiss
                snackbar.setAction(android.R.string.ok, view -> {
                    // Only dismiss message
                });
            }

            snackbar.show();
        }
    }

    /**
     * A {@link FragmentPagerAdapter} that returns a fragment corresponding to
     * one of fragment in welcome guide
     */
    public class PagerAdapter extends FragmentPagerAdapter {

        private Fragment mRenewFragment = null;
        private Hashtable<Integer, String> mFragmentsTags = new Hashtable<>();
        @FragmentId
        private int mLastFragmentId = WELCOME_FRAGMENT_ID;

        public PagerAdapter(@NonNull FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            // getItem is called to instantiate the fragment for the given page.
            // Return a PlaceholderFragment (defined as a static inner class below).
            switch (position) {
            case WELCOME_FRAGMENT_ID:
                return WelcomeFragment.newInstance();
            case USER_FRAGMENT_ID:
                return UserDetailFragment.newEmptyInstance();
            case PORTALS_LIST_FRAGMENT_ID:
                return PortalListFragment.newInstance();
            case PORTAL_FRAGMENT_ID:
                return PortalDetailFragment.newInstance();
            case CREDENTIALS_FRAGMENT_ID:
                return CredentialDetailFragment.newEmptyInstance(null, null);
            case HELP_FRAGMENT_ID:
                return HelpFragment.newInstance();
            default:
                throw new UnsupportedOperationException("Unsupported viewPager page: " + position);
            }
        }

        /**
         * Saves fragment tag for later, it's used for communication from activity to fragment. See
         * <a href="https://stackoverflow.com/a/29269509/1392034">this answer on StackOverflow</a>
         * for more.
         *
         * @return new created fragment or saved instance
         */
        @NonNull
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment fragment = (Fragment) super.instantiateItem(container, position);
            mFragmentsTags.put(position, fragment.getTag());
            return fragment;
        }

        /**
         * Gets lasted created Fragment on specific position
         *
         * @param position Position of Fragment in PagerAdapter
         * @return found Fragment or null if Fragment is not created or not exists
         */
        @Nullable
        Fragment getFragment(@FragmentId int position) {
            if (mFragmentsTags.containsKey(position)) {
                return getSupportFragmentManager().findFragmentByTag(mFragmentsTags.get(position));
            }
            return null;
        }

        @Override
        public int getCount() {
            return 6;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            switch (position) {
            case WELCOME_FRAGMENT_ID:
                return getString(R.string.welcome_activity_title);
            case USER_FRAGMENT_ID:
                return getString(R.string.new_user_screen_title);
            case PORTALS_LIST_FRAGMENT_ID:
                return getString(R.string.portal_list_screen_title);
            case PORTAL_FRAGMENT_ID:
                return getString(R.string.portal_screen_title);
            case CREDENTIALS_FRAGMENT_ID:
                return getString(R.string.credential_screen_title);
            case HELP_FRAGMENT_ID:
                return getString(R.string.help_screen_title);
            default:
                throw new UnsupportedOperationException("Unsupported viewPager page: " + position);
            }
        }
    }
}