Back to project page keepscore-android.
The source code is released under:
GNU General Public License
If you think the Android project keepscore-android listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/* Keep Score: keep track of player scores during a card game. Copyright (C) 2009 Michael Elsdrfer <http://elsdoerfer.name> /*www . j a va2 s.c o m*/ 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.elsdoerfer.keepscore; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; import android.view.ContextMenu; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.MenuItem.OnMenuItemClickListener; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.SimpleCursorAdapter; import android.widget.TextView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.SimpleCursorAdapter.ViewBinder; public class Setup extends Activity { // views protected ListView mExistingPlayersList; protected EditText mNewPlayerNameText; protected Button mAddNewPlayerOrStartButton; protected LinearLayout mExistingSessionsPanel; protected ListView mExistingSessionsList; // menu items public static final int CLEAR_PLAYERS_ID = Menu.FIRST; public static final int CONTINUE_GAME_ID = Menu.FIRST + 1; public static final int DELETE_GAME_ID = Menu.FIRST + 2; public static final int CLEAR_GAMES_ID = Menu.FIRST + 3; public static final int NAME_GAME_ID = Menu.FIRST + 4; protected MenuItem mClearPlayersItem; protected MenuItem mDeleteGameItem; protected MenuItem mClearGamesItem; DbAdapter mDb = new DbAdapter(this); // list objects/data protected ArrayList<String> mListOfPlayersArray; protected ArrayAdapter<String> mListOfPlayersAdapter; protected SimpleCursorAdapter mExistingSessionsAdapter; // storage keys public static final String LIST_OF_PLAYERS_KEY = "players"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.setup); // open database mDb = new DbAdapter(this); mDb.open(); // get views mExistingPlayersList = (ListView)findViewById(R.id.existing_players); mNewPlayerNameText = (EditText)findViewById(R.id.new_player_name); mAddNewPlayerOrStartButton = (Button)findViewById(R.id.add_new_player_or_start); mExistingSessionsPanel = (LinearLayout)findViewById(R.id.existing_sessions); mExistingSessionsList = (ListView)findViewById(R.id.existing_sessions_list); // prepare the list of players for a new session mListOfPlayersArray = savedInstanceState != null ? savedInstanceState.getStringArrayList(LIST_OF_PLAYERS_KEY) : new ArrayList<String>(); mListOfPlayersAdapter = new ArrayAdapter<String>( this, R.layout.player_list_item, mListOfPlayersArray); mExistingPlayersList.setAdapter(mListOfPlayersAdapter); // prepare the list of existing sessions final Cursor existingSessionListCursor = mDb.fetchAllSessions(); startManagingCursor(existingSessionListCursor); mExistingSessionsAdapter = new SimpleCursorAdapter( this, R.layout.session_list_item, existingSessionListCursor, new String[] { DbAdapter.SESSION_LABEL_VKEY, DbAdapter.SESSION_LAST_PLAYED_AT_KEY }, new int[] { android.R.id.text1, android.R.id.text2 }); mExistingSessionsAdapter.setViewBinder(new ViewBinder() { public boolean setViewValue(View view, Cursor cursor, int columnIndex) { int lastPlayedIndex = cursor.getColumnIndex(DbAdapter.SESSION_LAST_PLAYED_AT_KEY); if (columnIndex == lastPlayedIndex) { long now = new Date().getTime() / 1000; long lastPlayed = cursor.getLong(lastPlayedIndex) / 1000; // This code was adapted from http://code.google.com/p/connectbot/ String nice = getString(R.string.never); if (lastPlayed > 0) { int minutes = (int)((now - lastPlayed) / 60); if (minutes >= 60) { int hours = (minutes / 60); if (hours >= 24) { int days = (hours / 24); if (days > 30) { nice = new SimpleDateFormat("dd. MMM yyyy, HH:mm"). format(new Date(lastPlayed)); } else nice = getString(R.string.bind_days, days); } else nice = getString(R.string.bind_hours, hours); } else if (minutes == 0) nice = getString(R.string.just_now); else nice = getString(R.string.bind_minutes, minutes); } ((TextView)view).setText(nice); return true; } return false; } }); mExistingSessionsList.setAdapter(mExistingSessionsAdapter); // setup event handlers - we need to refer to the context in some of them final Context context = this; this.registerForContextMenu(mExistingSessionsList); mNewPlayerNameText.setOnKeyListener(new View.OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_ENTER) { // For now, ENTER cannot start a game, only add a new // player. First, we don't want it to happen by // accident. Second, for now for some reason pressing // ENTER would always start a new game, if we were to // simulate a click on the addNewPlayerOrStartButton // here (even with a onScreen keyboard). Would need to // be investigated. newPlayerNameSubmit(); return true; } return false; } }); mNewPlayerNameText.addTextChangedListener(new TextWatcher() { public void afterTextChanged(Editable s) { updateAddPlayerOrStartButton(); } public void beforeTextChanged(CharSequence s, int start, int count, int after) {} public void onTextChanged(CharSequence s, int start, int before, int count) {} }); mAddNewPlayerOrStartButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // "add a new player" mode if (!addPlayerOrStartButtonIsStartMode()) { newPlayerNameSubmit(); } // "start the game" mode else { long newId = mDb.createSession((String[]) mListOfPlayersArray.toArray(new String[0])); existingSessionListCursor.requery(); continueSession(newId); // TODO: The user will still see how the interface resets, // while the new activity is being loaded - not particularly // nice. Do something about it. mNewPlayerNameText.setText(""); mListOfPlayersAdapter.clear(); updateUI(); } } }); mExistingPlayersList.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> arg0, View view, int position, long arg3) { final String selectedPlayer = mListOfPlayersAdapter.getItem(position); new AlertDialog.Builder(context) .setIcon(android.R.drawable.ic_dialog_alert) .setTitle(getResources().getString(R.string.confirm_rm_player, selectedPlayer)) .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mListOfPlayersAdapter.remove(selectedPlayer); updateUI(); } }) .setNegativeButton(android.R.string.no, null) .create().show(); } }); mExistingSessionsList.setOnItemSelectedListener(new OnItemSelectedListener() { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { sessionListSelectionChanged(); } public void onNothingSelected(AdapterView<?> parent) { sessionListSelectionChanged(); } }); mExistingSessionsList.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { continueSession(id); } }); // initial update updateUI(); } @Override public void onDestroy() { super.onDestroy(); mDb.close(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putStringArrayList(LIST_OF_PLAYERS_KEY, mListOfPlayersArray); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); mClearPlayersItem = menu.add(0, CLEAR_PLAYERS_ID, 0, R.string.clear_players); mClearPlayersItem.setIcon(R.drawable.ic_menu_close_clear_cancel); mDeleteGameItem = menu.add(0, DELETE_GAME_ID, 0, R.string.delete_session); mDeleteGameItem.setIcon(R.drawable.ic_menu_delete); mClearGamesItem = menu.add(0, CLEAR_GAMES_ID, 0, R.string.clear_sessions); mClearGamesItem.setIcon(R.drawable.ic_menu_close_clear_cancel); // setup initial visibilities updateUI(); sessionListSelectionChanged(); return true; } public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case DELETE_GAME_ID: deleteSession(mExistingSessionsList.getSelectedItemId()); return true; case CLEAR_GAMES_ID: new AlertDialog.Builder(this) .setIcon(android.R.drawable.ic_dialog_alert) .setTitle(R.string.clear_sessions) .setMessage(R.string.confirm_rm_all_sessions) .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mDb.clearSessions(); mExistingSessionsAdapter.getCursor().requery(); updateUI(); } }) .setNegativeButton("No", null) .create().show(); return true; case CLEAR_PLAYERS_ID: mListOfPlayersAdapter.clear(); updateUI(); return true; } return false; } public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo; menu.setHeaderTitle(((TextView)info.targetView.findViewById(android.R.id.text1)).getText()); menu.add(R.string.continue_session).setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { continueSession(info.id); return true; } }); String sessionName = mDb.getSessionName(info.id); menu.add(!sessionName.equals("") ? R.string.rename_session : R.string.name_session). setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { renameSession(info.id); return true; } }); menu.add(R.string.delete_session).setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { deleteSession(info.id); return true; } }); } private void newPlayerNameSubmit() { String playerName = mNewPlayerNameText.getText().toString().trim(); if (playerName.length()==0) return; addPlayerToNewGame(playerName); // clear field for new player mNewPlayerNameText.setText(""); mNewPlayerNameText.requestFocus(); // Need to call this to workaround 1.0 bug where setText() does // not cause the TextWatcher onChange events. updateAddPlayerOrStartButton(); } protected void addPlayerToNewGame(String playerName) { mListOfPlayersAdapter.add(playerName); updateUI(); } protected void continueSession(long id) { Intent intent = new Intent(this, Game.class); intent.putExtra(DbAdapter.SESSION_ID_KEY, id); startActivity(intent); } protected void renameSession(final long id) { final EditText input = new EditText(this); // TODO: Currently, this dialog does not persist across // orientation changes. Fix. AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.custom_session_name); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { String value = input.getText().toString(); mDb.setSessionName(id, value); mExistingSessionsAdapter.getCursor().requery(); updateUI(); } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) {} }); // TODO: Pressing ENTER in the edit field will jump to the Cancel // button if the caret position is above it (i.e. the text entered // long enough). This seems to be the standard behavior though, // and can also be seen in the Bluetooth "Device name" option of // the builtin settings app. AlertDialog alert = builder.create(); input.setText(mDb.getSessionName(id)); input.setInputType(InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); alert.setView(input, 10, 10, 10, 7); input.selectAll(); alert.show(); } protected void deleteSession(long id) { mDb.deleteSession(id); mExistingSessionsAdapter.getCursor().requery(); updateUI(); } protected void sessionListSelectionChanged() { if (mDeleteGameItem!=null) mDeleteGameItem.setEnabled(mExistingSessionsList.getSelectedItem() != null); } /** * Returns true if the button is in start mode, false otherwise. */ protected boolean addPlayerOrStartButtonIsStartMode() { // allow to start a new game only if min. 2 players return (mNewPlayerNameText.getText().toString().length() == 0 && mListOfPlayersAdapter.getCount() >= 2); } protected void updateAddPlayerOrStartButton() { Drawable drawable = null; if (addPlayerOrStartButtonIsStartMode()) drawable = getResources().getDrawable(R.drawable.ic_menu_play_clip_small); else drawable = getResources().getDrawable(R.drawable.ic_menu_add_small); mAddNewPlayerOrStartButton.setCompoundDrawablesWithIntrinsicBounds( drawable, null, null, null); } protected void updateUI() { // Hide "existing session" list once the user starts to add // players for a new game. This is mostly for layout reasons, // because we apparently can't really have two lists in the // same screen unless both are fixed height (the first // list would push elements below it out of the screen) (*). // // So we basically hide the session list when the player // starts to use the player list. // // (*) We could possible work with a parent ScrollView and // making both lists wrap_content, i.e. the whole screen // would scroll, through both lists and the controls in // between. This wouldn't make for very good user interface // though, since the user would be responsible to scrolling // the "player name" TextEdit into view when he wants to use it. if (!mListOfPlayersAdapter.isEmpty()) { LinearLayout.LayoutParams params; params = (LinearLayout.LayoutParams) mExistingPlayersList.getLayoutParams(); params.weight = 1; mExistingPlayersList.setLayoutParams(params); mExistingSessionsPanel.setVisibility(View.GONE); } else { LinearLayout.LayoutParams params; params = (LinearLayout.LayoutParams) mExistingPlayersList.getLayoutParams(); params.weight = 0; mExistingPlayersList.setLayoutParams(params); // Also hide the whole sessions panel if there aren't any sessions if (mExistingSessionsAdapter.isEmpty()) mExistingSessionsPanel.setVisibility(View.GONE); else mExistingSessionsPanel.setVisibility(View.VISIBLE); } // Show/hide/enable menu items depending on the features // and controls currently visible. if (mDeleteGameItem != null) { // Menu might not have been created yet boolean editingPlayers = !mListOfPlayersAdapter.isEmpty(); boolean sessionsExist = !mExistingSessionsAdapter.isEmpty(); mDeleteGameItem.setVisible(!editingPlayers); mClearGamesItem.setVisible(!editingPlayers); mClearGamesItem.setEnabled(sessionsExist); mClearPlayersItem.setVisible(editingPlayers); } updateAddPlayerOrStartButton(); } }