ca.rmen.android.scrumchatter.team.Teams.java Source code

Java tutorial

Introduction

Here is the source code for ca.rmen.android.scrumchatter.team.Teams.java

Source

/*
 * Copyright 2013-2017 Carmen Alvarez
 *
 * This file is part of Scrum Chatter.
 *
 * Scrum Chatter 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.
 *
 * Scrum Chatter 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 Scrum Chatter. If not, see <http://www.gnu.org/licenses/>.
 */
package ca.rmen.android.scrumchatter.team;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.MainThread;
import android.support.annotation.WorkerThread;
import android.support.v4.app.FragmentActivity;
import android.text.TextUtils;

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

import ca.rmen.android.scrumchatter.Constants;
import ca.rmen.android.scrumchatter.R;
import ca.rmen.android.scrumchatter.dialog.DialogFragmentFactory;
import ca.rmen.android.scrumchatter.dialog.InputDialogFragment.InputValidator;
import ca.rmen.android.scrumchatter.provider.TeamColumns;
import ca.rmen.android.scrumchatter.settings.Prefs;
import ca.rmen.android.scrumchatter.util.Log;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;

/**
 * Provides both UI and DB logic regarding the management of teams: renaming, choosing, creating, and deleting teams.
 */
public class Teams {
    private static final String TAG = Constants.TAG + "/" + Teams.class.getSimpleName();
    public static final String EXTRA_TEAM_URI = "team_uri";
    public static final String EXTRA_TEAM_ID = "team_id";
    private static final String EXTRA_TEAM_NAME = "team_name";
    private final FragmentActivity mActivity;

    public static class Team {
        private final Uri teamUri;
        public final String teamName;

        private Team(Uri teamUri, String teamName) {
            this.teamUri = teamUri;
            this.teamName = teamName;
        }

        @Override
        public String toString() {
            return "Team [teamUri=" + teamUri + ", teamName=" + teamName + "]";
        }

    }

    public static final class TeamsData {
        public final Team currentTeam;
        public final List<Team> teams;

        private TeamsData(Team currentTeam, List<Team> teams) {
            this.currentTeam = currentTeam;
            this.teams = teams;
        }
    }

    public Teams(FragmentActivity activity) {
        mActivity = activity;
    }

    /**
     * Upon selecting a team, update the shared preference for the selected team.
     */
    public void switchTeam(final CharSequence teamName) {
        Log.v(TAG, "switchTeam " + teamName);
        Schedulers.io().scheduleDirect(() -> {
            Cursor c = mActivity.getContentResolver().query(TeamColumns.CONTENT_URI,
                    new String[] { TeamColumns._ID }, TeamColumns.TEAM_NAME + " = ?",
                    new String[] { String.valueOf(teamName) }, null);
            if (c != null) {
                try {
                    c.moveToFirst();
                    if (c.getCount() == 1) {
                        int teamId = c.getInt(0);
                        Prefs.getInstance(mActivity).setTeamId(teamId);
                    } else {
                        Log.wtf(TAG, "Found " + c.getCount() + " teams for " + teamName);
                    }

                } finally {
                    c.close();
                }
            }
        });
    }

    /**
     * Show a dialog with a text input for the new team name. Validate that the team doesn't already exist. Upon pressing "OK", create the team.
     */
    public void promptCreateTeam() {
        Log.v(TAG, "promptCreateTeam");
        DialogFragmentFactory.showInputDialog(mActivity, mActivity.getString(R.string.action_new_team),
                mActivity.getString(R.string.hint_team_name), null, TeamNameValidator.class, R.id.action_team,
                null);
    }

    public void createTeam(final String teamName) {
        Log.v(TAG, "createTeam, name=" + teamName);
        // Ignore an empty name.
        if (!TextUtils.isEmpty(teamName)) {
            // Create the new team in a background thread.
            Schedulers.io().scheduleDirect(() -> {
                ContentValues values = new ContentValues(1);
                values.put(TeamColumns.TEAM_NAME, teamName);
                Uri newTeamUri = mActivity.getContentResolver().insert(TeamColumns.CONTENT_URI, values);
                if (newTeamUri != null) {
                    int newTeamId = Integer.valueOf(newTeamUri.getLastPathSegment());
                    Prefs.getInstance(mActivity).setTeamId(newTeamId);
                }
            });
        }
    }

    /**
     * Retrieve the currently selected team. Show a dialog with a text input to rename this team. Validate that the new name doesn't correspond to any other
     * existing team. Upon pressing ok, rename the current team.
     */
    public void promptRenameTeam(final Team team) {
        Log.v(TAG, "promptRenameTeam, team=" + team);
        if (team != null) {
            // Show a dialog to input a new team name for the current team.
            Bundle extras = new Bundle(1);
            extras.putParcelable(EXTRA_TEAM_URI, team.teamUri);
            extras.putString(EXTRA_TEAM_NAME, team.teamName);
            DialogFragmentFactory.showInputDialog(mActivity, mActivity.getString(R.string.action_team_rename),
                    mActivity.getString(R.string.hint_team_name), team.teamName, TeamNameValidator.class,
                    R.id.action_team_rename, extras);
        }
    }

    public void renameTeam(final Uri teamUri, final String teamName) {
        Log.v(TAG, "renameTeam, uri = " + teamUri + ", name = " + teamName);

        // Ignore an empty name.
        if (!TextUtils.isEmpty(teamName)) {
            // Rename the team in a background thread.
            Schedulers.io().scheduleDirect(() -> {
                ContentValues values = new ContentValues(1);
                values.put(TeamColumns.TEAM_NAME, teamName);
                mActivity.getContentResolver().update(teamUri, values, null, null);
            });
        }
    }

    /**
     * Shows a confirmation dialog to the user to delete a team.
     */
    @MainThread
    public void confirmDeleteTeam(final Team team) {
        Log.v(TAG, "confirmDeleteTeam, team = " + team);

        getTeamCount().subscribe(teamCount -> {
            // We need at least one team in the app.
            if (teamCount <= 1) {
                DialogFragmentFactory.showInfoDialog(mActivity, R.string.action_team_delete,
                        R.string.dialog_error_one_team_required);
            }
            // Delete this team
            else if (team != null) {
                Bundle extras = new Bundle(1);
                extras.putParcelable(EXTRA_TEAM_URI, team.teamUri);
                DialogFragmentFactory.showConfirmDialog(mActivity, mActivity.getString(R.string.action_team_delete),
                        mActivity.getString(R.string.dialog_message_delete_team_confirm, team.teamName),
                        R.id.action_team_delete, extras);
            }
        });
    }

    /**
     * Deletes the team and all its members from the DB.
     */
    public void deleteTeam(final Uri teamUri) {
        Log.v(TAG, "deleteTeam, uri = " + teamUri);
        Schedulers.io().scheduleDirect(() -> {
            // delete this team
            mActivity.getContentResolver().delete(teamUri, null, null);
            // pick another current team
            selectFirstTeam();
        });
    }

    /**
     * Select the first team in our DB.
     */
    private Team selectFirstTeam() {
        Cursor c = mActivity.getContentResolver().query(TeamColumns.CONTENT_URI,
                new String[] { TeamColumns._ID, TeamColumns.TEAM_NAME }, null, null, null);
        if (c != null) {
            try {
                if (c.moveToFirst()) {
                    int teamId = c.getInt(0);
                    String teamName = c.getString(1);
                    Prefs.getInstance(mActivity).setTeamId(teamId);
                    Uri teamUri = Uri.withAppendedPath(TeamColumns.CONTENT_URI, String.valueOf(teamId));
                    return new Team(teamUri, teamName);
                }
            } finally {
                c.close();
            }
        }
        return null;
    }

    /**
     * @return the Team currently selected by the user.
     */
    @WorkerThread
    private Team getCurrentTeam() {
        // Retrieve the current team name and construct a uri for the team based on the current team id.
        int teamId = Prefs.getInstance(mActivity).getTeamId();
        Uri teamUri = Uri.withAppendedPath(TeamColumns.CONTENT_URI, String.valueOf(teamId));
        Cursor c = mActivity.getContentResolver().query(teamUri, new String[] { TeamColumns.TEAM_NAME }, null, null,
                null);
        if (c != null) {
            try {
                if (c.moveToFirst()) {
                    String teamName = c.getString(0);
                    return new Team(teamUri, teamName);
                }
            } finally {
                c.close();
            }
        }
        Log.wtf(TAG, "Could not get the current team", new Throwable());
        return selectFirstTeam();
    }

    /**
     * Query the teams table, and return a list of all teams, and the current team.
     */
    public Single<TeamsData> getAllTeams() {
        Log.v(TAG, "getAllTeams");
        return Single.fromCallable(() -> {
            Log.v(TAG, "getAllTeams work");
            List<Team> teams = new ArrayList<>();
            Cursor c = mActivity.getContentResolver().query(TeamColumns.CONTENT_URI,
                    new String[] { TeamColumns._ID, TeamColumns.TEAM_NAME }, null, null,
                    TeamColumns.TEAM_NAME + " COLLATE NOCASE");

            if (c != null) {
                try {
                    // Add the names of all the teams
                    while (c.moveToNext()) {
                        int teamId = c.getInt(0);
                        String teamName = c.getString(1);
                        Uri teamUri = Uri.withAppendedPath(TeamColumns.CONTENT_URI, String.valueOf(teamId));
                        teams.add(new Team(teamUri, teamName));
                    }
                } finally {
                    c.close();
                }
            }
            return new TeamsData(getCurrentTeam(), teams);

        }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
    }

    /**
     * @return the total number of teams
     */
    public Single<Integer> getTeamCount() {
        return Single.fromCallable(() -> {

            Cursor c = mActivity.getContentResolver().query(TeamColumns.CONTENT_URI, new String[] { "count(*)" },
                    null, null, null);
            if (c != null) {
                try {
                    if (c.moveToFirst())
                        return c.getInt(0);
                } finally {
                    c.close();
                }
            }
            return 0;
        }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
    }

    public Single<Team> readCurrentTeam() {
        return Single.fromCallable(this::getCurrentTeam).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
    }

    /**
     * Returns an error if the user entered the name of an existing team. To prevent renaming or creating multiple teams with the same name.
     */
    public static class TeamNameValidator implements InputValidator { // NO_UCD (use private)

        public TeamNameValidator() {
        }

        @Override
        public String getError(Context context, CharSequence input, Bundle extras) {
            // teamName is optional. If given, we won't show an error for renaming a team to its current name.
            String teamName = extras == null ? null : extras.getString(Teams.EXTRA_TEAM_NAME);

            // In the case of changing a team name, teamName will not be null, and we won't show an error if the name the user enters is the same as the existing team.
            // In the case of adding a new team, mTeamName will be null.
            if (!TextUtils.isEmpty(teamName) && !TextUtils.isEmpty(input) && teamName.equals(input.toString()))
                return null;

            // Query for a team with this name.
            Cursor cursor = context.getContentResolver().query(TeamColumns.CONTENT_URI, new String[] { "count(*)" },
                    TeamColumns.TEAM_NAME + "=?", new String[] { String.valueOf(input) }, null);

            // Now Check if the team member exists.
            if (cursor != null) {
                if (cursor.moveToFirst()) {
                    int existingTeamCount = cursor.getInt(0);
                    cursor.close();
                    if (existingTeamCount > 0)
                        return context.getString(R.string.error_team_exists, input);
                }
            }
            return null;
        }
    }

}