org.pixmob.fm2.ui.AccountsFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.pixmob.fm2.ui.AccountsFragment.java

Source

/*
 * Copyright (C) 2012 Pixmob (http://github.com/pixmob)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.pixmob.fm2.ui;

import static org.pixmob.fm2.Constants.DEBUG;
import static org.pixmob.fm2.Constants.TAG;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.pixmob.fm2.R;
import org.pixmob.fm2.model.Account;
import org.pixmob.fm2.model.AccountRepository;
import org.pixmob.fm2.services.SyncService;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemClock;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.app.SupportActivity;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.Menu;
import android.support.v4.view.MenuItem;
import android.text.method.PasswordTransformationMethod;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.bugsense.trace.BugSenseHandler;

/**
 * {@link Fragment} displaying {@link Account} instances.
 * @author Pixmob
 */
public class AccountsFragment extends ListFragment
        implements LoaderCallbacks<List<Account>>, SyncService.Listener, ServiceConnection {
    private AccountAdapter accountAdapter;
    private SyncService syncService;
    private Intent syncServiceIntent;
    private boolean dualPane;
    private int selectedAccountIndex;

    @Override
    public void onSyncDone() {
        // Update the list with new content.
        getLoaderManager().getLoader(0).onContentChanged();
    }

    @Override
    public void onSyncError(Exception cause) {
        Toast.makeText(getActivity(), R.string.error_sync_failed, Toast.LENGTH_SHORT).show();
    }

    @Override
    public Loader<List<Account>> onCreateLoader(int id, Bundle args) {
        if (DEBUG) {
            Log.d(TAG, "AccountsFragment.onCreateLoader");
        }

        return new AccountListLoader(getActivity());
    }

    @Override
    public void onLoaderReset(Loader<List<Account>> loader) {
        if (DEBUG) {
            Log.d(TAG, "AccountsFragment.onLoaderReset");
        }

        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // Clear the data in the adapter.
                accountAdapter.setData(null);
            }
        });
    }

    @Override
    public void onLoadFinished(Loader<List<Account>> loader, final List<Account> data) {
        if (DEBUG) {
            Log.d(TAG, "AccountsFragment.onLoadFinished");
        }

        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // The list should now be shown.
                if (isResumed()) {
                    setListShown(true);
                } else {
                    setListShownNoAnimation(true);
                }

                // Set the new data in the adapter.
                accountAdapter.setData(data);

                if (dualPane) {
                    getListView().setItemChecked(selectedAccountIndex, false);
                }
            }
        });

        if (dualPane) {
            // FIXME Remove this hack
            new Thread() {
                @Override
                public void run() {
                    SystemClock.sleep(1000);

                    final AccountDetailsFragment details = (AccountDetailsFragment) getSupportFragmentManager()
                            .findFragmentById(R.id.account_details);
                    if (details != null) {
                        final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
                        ft.remove(details);
                        ft.commit();
                    }
                }
            }.start();
        }
    }

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

        setEmptyText(getString(R.string.no_account));
        setHasOptionsMenu(true);

        syncServiceIntent = new Intent(getActivity(), SyncService.class);

        accountAdapter = new AccountAdapter(getActivity());
        setListAdapter(accountAdapter);
        setListShown(false);

        final View detailsFrame = getActivity().findViewById(R.id.account_details);
        dualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;

        if (savedInstanceState != null) {
            selectedAccountIndex = savedInstanceState.getInt("selectedAccountIndex", 0);
        }

        if (dualPane) {
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        }

        // Start user account loading.
        getLoaderManager().initLoader(0, null, this);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("selectedAccountIndex", selectedAccountIndex);
    }

    @Override
    public void onStart() {
        super.onStart();

        // Bind the service with this fragment in order to register a listener.
        getActivity().bindService(syncServiceIntent, this, Context.BIND_AUTO_CREATE);
    }

    @Override
    public void onStop() {
        super.onStop();

        // The service is not used anymore by this fragment.
        getActivity().unbindService(this);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        menu.add(Menu.NONE, R.string.menu_refresh, Menu.NONE, R.string.menu_refresh)
                .setIcon(R.drawable.ic_menu_refresh).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
        menu.add(Menu.NONE, R.string.menu_add_account, Menu.NONE, R.string.menu_add_account)
                .setIcon(R.drawable.ic_menu_invite).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
        menu.add(Menu.NONE, R.string.menu_prefs, Menu.NONE, R.string.menu_prefs)
                .setIcon(R.drawable.ic_menu_preferences).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.string.menu_refresh:
            onActionRefresh();
            break;
        case R.string.menu_add_account:
            onActionAddAccount();
            break;
        case R.string.menu_prefs:
            onActionPreferences();
            break;
        }
        return super.onOptionsItemSelected(item);
    }

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

        // Release resources.
        accountAdapter = null;
        syncServiceIntent = null;
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);
        selectAccount(position);
    }

    private void selectAccount(int position) {
        if (position >= accountAdapter.getCount() || accountAdapter.getCount() == 0) {
            selectedAccountIndex = 0;
            if (dualPane) {
                final AccountDetailsFragment details = (AccountDetailsFragment) getSupportFragmentManager()
                        .findFragmentById(R.id.account_details);
                if (details != null) {
                    final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
                    ft.remove(details);
                    ft.commit();
                }
            }
        } else {
            selectedAccountIndex = position;

            final Account account = accountAdapter.getItem(position);
            if (dualPane) {
                getListView().setItemChecked(position, true);

                // Display account details in a fragment.
                AccountDetailsFragment details = (AccountDetailsFragment) getSupportFragmentManager()
                        .findFragmentById(R.id.account_details);
                if (details == null || details.getAccount().id != account.id) {
                    details = AccountDetailsFragment.newInstance(account);

                    final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
                    ft.replace(R.id.account_details, details);
                    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                    ft.commit();
                }
            } else {
                // Open account details with a new activity.
                startActivity(new Intent(getActivity(), AccountDetails.class).putExtra(AccountDetails.EXTRA_ACCOUNT,
                        account));
            }
        }
    }

    private void onActionRefresh() {
        // Start the synchronization service in background.
        getActivity().startService(syncServiceIntent);

        final AccountDetailsFragment details = (AccountDetailsFragment) getSupportFragmentManager()
                .findFragmentById(R.id.account_details);
        if (details != null) {
            details.refresh();
        }
    }

    private void onActionAddAccount() {
        AddAccountDialogFragment.newInstance().show(getSupportFragmentManager(), "dialog");
    }

    private void onActionPreferences() {
        startActivity(new Intent(getActivity(), Preferences.class));
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // The service is now bound to this fragment: we can get its instance
        // and register as a listener.
        final SyncService.LocalBinder binder = (SyncService.LocalBinder) service;
        syncService = binder.getService();
        syncService.setListener(this);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        // The service is gone: make sure the listener is unset to prevent
        // memory leaks.
        syncService.setListener(null);
        syncService = null;
    }

    /**
     * Internal class for displaying {@link Account} instances.
     * @author Pixmob
     */
    private static class AccountAdapter extends ArrayAdapter<Account> {
        private final LayoutInflater layoutInflater;

        public AccountAdapter(final Context context) {
            super(context, android.R.layout.simple_list_item_2);
            layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }

        /**
         * Set the account list to display. If <code>accounts</code> is
         * <code>null</code>, the list is cleared.
         */
        public void setData(List<Account> accounts) {
            setNotifyOnChange(false);
            clear();
            if (accounts != null) {
                for (final Account account : accounts) {
                    add(account);
                }
            }
            notifyDataSetChanged();
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            final View row;
            if (convertView == null) {
                // Reusing the same view will save memory.
                row = layoutInflater.inflate(R.layout.account_row, parent, false);
            } else {
                row = convertView;
            }

            final Account account = getItem(position);
            row.setTag(account);

            TextView ctv = (TextView) row.findViewById(R.id.account_name);
            if (account.name == null) {
                ctv.setText(R.string.unknown_contract);
            } else {
                ctv.setText(account.name);
            }

            ctv = (TextView) row.findViewById(R.id.account_number);
            if (account.phoneNumber == null) {
                ctv.setText(R.string.unknown_phone_number);
            } else {
                ctv.setText(account.phoneNumber);
            }

            ctv = (TextView) row.findViewById(R.id.account_status);
            if (account.status == 0) {
                ctv.setText(R.string.unknown_status_step);
            } else {
                ctv.setText(String.format(getContext().getString(R.string.status_step), account.status));
            }

            return row;
        }
    }

    /**
     * Background task for loading accounts.
     * @author Pixmob
     */
    private static class AccountListLoader extends AsyncTaskLoader<List<Account>> {
        private List<Account> accounts;

        public AccountListLoader(final Context context) {
            super(context);
        }

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

            // Force account loading, since we do not keep track of account
            // updates.
            forceLoad();
        }

        @Override
        public List<Account> loadInBackground() {
            if (accounts != null) {
                deliverResult(accounts);
            }

            if (DEBUG) {
                Log.d(TAG, "Loading user accounts");
            }
            List<Account> newAccounts = Collections.emptyList();
            final AccountRepository accountRepository = new AccountRepository(getContext());
            try {
                newAccounts = accountRepository.list();
                Collections.sort(newAccounts, AccountComparator.INSTANCE);
            } catch (Exception e) {
                Log.e(TAG, "Account loading failed", e);
                BugSenseHandler.log(TAG, e);
            }

            accounts = newAccounts;
            return newAccounts;
        }
    }

    /**
     * {@link Account} comparator.
     * @author Pixmob
     */
    private static class AccountComparator implements Comparator<Account> {
        public static final Comparator<Account> INSTANCE = new AccountComparator();

        @Override
        public int compare(Account a1, Account a2) {
            final String n1 = a1.phoneNumber;
            final String n2 = a2.phoneNumber;
            if (n1 == null) {
                return 1;
            }
            if (n2 == null) {
                return 0;
            }
            return n1.compareTo(n2);
        }
    }

    /**
     * Dialog for creating a new account.
     * @author Pixmob
     */
    public static class AddAccountDialogFragment extends DialogFragment {
        public static AddAccountDialogFragment newInstance() {
            return new AddAccountDialogFragment();
        }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            final LayoutInflater inflater = LayoutInflater.from(getActivity());
            final View content = inflater.inflate(R.layout.add_account_dialog, null);

            final EditText loginField = (EditText) content.findViewById(R.id.account_login);
            final EditText passwordField = (EditText) content.findViewById(R.id.account_password);
            final CheckBox showPassword = (CheckBox) content.findViewById(R.id.show_password);
            showPassword.setOnCheckedChangeListener(new OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    final int i = passwordField.getSelectionStart();
                    passwordField.setTransformationMethod(isChecked ? null : new PasswordTransformationMethod());
                    passwordField.setSelection(i);
                }
            });

            return new AlertDialog.Builder(getActivity()).setTitle(R.string.menu_add_account)
                    .setIcon(android.R.drawable.ic_dialog_alert).setView(content)
                    .setPositiveButton(R.string.dialog_add, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            final String login = loginField.getText().toString().trim();
                            final String password = passwordField.getText().toString().trim();
                            new CreateAccountTask((SupportActivity) getActivity(), login, password).execute();
                        }
                    }).setNegativeButton(R.string.dialog_cancel, null).create();
        }
    }

    private static class CreateAccountTask extends AsyncTask<Void, Void, Void> {
        private final SupportActivity context;
        private final String login;
        private final String password;
        private boolean missingData;

        public CreateAccountTask(final SupportActivity context, final String login, final String password) {
            this.context = context;
            this.login = login.trim();
            this.password = password.trim();
        }

        @Override
        protected Void doInBackground(Void... params) {
            if (login.length() == 0 || password.length() == 0) {
                missingData = true;
            } else {
                final AccountRepository accountRepository = new AccountRepository(context.getApplicationContext());
                accountRepository.create(login, password);
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);
            if (missingData) {
                new MissingAccountDataDialog().show(context.getSupportFragmentManager(), "error");
            } else {
                Toast.makeText(context.getApplicationContext(), context.getString(R.string.adding_account),
                        Toast.LENGTH_SHORT).show();
                context.startService(new Intent(context.getApplicationContext(), SyncService.class));
            }
        }
    }

    public static class MissingAccountDataDialog extends DialogFragment {
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return new AlertDialog.Builder(getActivity()).setTitle(R.string.dialog_error)
                    .setMessage(R.string.missing_data_for_adding_account)
                    .setPositiveButton(android.R.string.ok, null).setIcon(android.R.drawable.ic_dialog_alert)
                    .create();
        }
    }
}