Merge branch 'android_branch' into fix_dlgdelegate

This commit is contained in:
Eric House 2012-12-31 06:25:49 -08:00
commit d1941090c5
35 changed files with 1260 additions and 281 deletions

View file

@ -25,16 +25,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
> >
<Button android:id="@+id/exchange_commit" <Button android:id="@+id/exchange_commit"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/button_trade_commit" android:text="@string/button_trade_commit"
style="@style/spaced_buttons"
/> />
<Button android:id="@+id/exchange_cancel" <Button android:id="@+id/exchange_cancel"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/button_trade_cancel" android:text="@string/button_trade_cancel"
style="@style/spaced_buttons"
/> />
</LinearLayout> </LinearLayout>

View file

@ -26,9 +26,7 @@
> >
<Button android:id="@+id/button_rescan" <Button android:id="@+id/button_rescan"
android:text="@string/bt_pick_rescan_button" android:text="@string/bt_pick_rescan_button"
android:layout_width="fill_parent" style="@style/spaced_buttons"
android:layout_height="wrap_content"
android:layout_weight="1"
/> />
<!-- <Button android:id="@+id/button_reconfigure" --> <!-- <Button android:id="@+id/button_reconfigure" -->
<!-- android:text="@string/bt_pick_reconfig_button" --> <!-- android:text="@string/bt_pick_reconfig_button" -->
@ -38,9 +36,7 @@
<!-- /> --> <!-- /> -->
<Button android:id="@+id/button_clear" <Button android:id="@+id/button_clear"
android:text="@string/bt_pick_clear_button" android:text="@string/bt_pick_clear_button"
android:layout_width="fill_parent" style="@style/spaced_buttons"
android:layout_height="wrap_content"
android:layout_weight="1"
/> />
</LinearLayout> </LinearLayout>

View file

@ -4,9 +4,7 @@
android:orientation="vertical" android:orientation="vertical"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:paddingLeft="8dp" >
android:paddingRight="8dp">
<LinearLayout android:id="@+id/empty_games_list" <LinearLayout android:id="@+id/empty_games_list"
android:orientation="vertical" android:orientation="vertical"
android:layout_width="fill_parent" android:layout_width="fill_parent"
@ -18,21 +16,25 @@
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:background="#FF202020" android:background="#FF202020"
android:gravity="center" android:gravity="center"
android:paddingLeft="8dp"
android:paddingRight="8dp"
/> />
<TextView android:layout_height="wrap_content" <TextView android:layout_height="wrap_content"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:text="@string/empty_games_list2" android:text="@string/empty_games_list2"
android:background="#FF202020" android:background="#FF202020"
android:gravity="center" android:gravity="center"
android:paddingLeft="8dp"
android:paddingRight="8dp"
/> />
</LinearLayout> </LinearLayout>
<ListView android:id="@id/android:list" <ExpandableListView android:id="@id/android:list"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:layout_weight="1" android:layout_weight="1"
android:drawSelectorOnTop="false" android:drawSelectorOnTop="false"
/> />
<TextView android:id="@+id/empty_list_msg" <TextView android:id="@+id/empty_list_msg"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
@ -41,11 +43,21 @@
android:text="@string/empty_list_msg" android:text="@string/empty_list_msg"
/> />
<Button android:id="@+id/new_game" <LinearLayout android:id="@+id/new_buttons"
android:layout_width="fill_parent" android:orientation="horizontal"
android:layout_height="wrap_content" android:layout_width="fill_parent"
android:layout_gravity="right" android:layout_height="wrap_content"
android:text="@string/button_new_game" android:paddingLeft="8dp"
/> android:paddingRight="8dp"
>
<Button android:id="@+id/new_game"
android:text="@string/button_new_game"
style="@style/spaced_buttons"
/>
<Button android:id="@+id/new_group"
android:text="@string/button_new_group"
style="@style/spaced_buttons"
/>
</LinearLayout>
</LinearLayout> </LinearLayout>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<org.eehouse.android.xw4.GameListGroup
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/game_name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:textStyle="italic"
android:background="#FF7F7F7F"
/>

View file

@ -12,6 +12,7 @@
android:focusable="true" android:focusable="true"
android:clickable="true" android:clickable="true"
android:background="@android:drawable/list_selector_background" android:background="@android:drawable/list_selector_background"
android:paddingLeft="12dp"
> >
<TextView android:id="@+id/view_unloaded" <TextView android:id="@+id/view_unloaded"

View file

@ -51,16 +51,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
> >
<Button android:id="@+id/newgame_local" <Button android:id="@+id/newgame_local"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_local" android:text="@string/newgame_local"
style="@style/spaced_buttons"
/> />
<Button android:id="@+id/newgame_local_config" <Button android:id="@+id/newgame_local_config"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_local_config" android:text="@string/newgame_local_config"
style="@style/spaced_buttons"
/> />
</LinearLayout> </LinearLayout>
@ -93,17 +89,13 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
> >
<Button android:id="@+id/newgame_invite" <Button android:id="@+id/newgame_invite"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_invite" android:text="@string/newgame_invite"
style="@style/spaced_buttons"
/> />
<Button android:id="@+id/newgame_net_config" <Button android:id="@+id/newgame_net_config"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_net_config" android:text="@string/newgame_net_config"
style="@style/spaced_buttons"
/> />
</LinearLayout> </LinearLayout>
@ -160,17 +152,13 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
> >
<Button android:id="@+id/newgame_invite_sms" <Button android:id="@+id/newgame_invite_sms"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_invite" android:text="@string/newgame_invite"
style="@style/spaced_buttons"
/> />
<Button android:id="@+id/newgame_sms_config" <Button android:id="@+id/newgame_sms_config"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_net_config" android:text="@string/newgame_net_config"
style="@style/spaced_buttons"
/> />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@ -228,17 +216,13 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
> >
<Button android:id="@+id/newgame_invite_bt" <Button android:id="@+id/newgame_invite_bt"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_invite" android:text="@string/newgame_invite"
style="@style/spaced_buttons"
/> />
<Button android:id="@+id/newgame_bt_config" <Button android:id="@+id/newgame_bt_config"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_net_config" android:text="@string/newgame_net_config"
style="@style/spaced_buttons"
/> />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View file

@ -20,16 +20,12 @@
android:layout_weight="0" android:layout_weight="0"
> >
<Button android:id="@+id/revert_colors" <Button android:id="@+id/revert_colors"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/button_revert_colors" android:text="@string/button_revert_colors"
style="@style/spaced_buttons"
/> />
<Button android:id="@+id/revert_all" <Button android:id="@+id/revert_all"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/button_revert_all" android:text="@string/button_revert_all"
style="@style/spaced_buttons"
/> />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View file

@ -41,9 +41,7 @@
> >
<Button android:id="@+id/button_add" <Button android:id="@+id/button_add"
android:text="@string/button_sms_add" android:text="@string/button_sms_add"
android:layout_height="fill_parent" style="@style/spaced_buttons"
android:layout_width="wrap_content"
android:layout_weight="1"
/> />
<ImageButton android:id="@+id/manual_add_button" <ImageButton android:id="@+id/manual_add_button"
android:layout_height="fill_parent" android:layout_height="fill_parent"
@ -53,9 +51,7 @@
/> />
<Button android:id="@+id/button_clear" <Button android:id="@+id/button_clear"
android:text="@string/bt_pick_clear_button" android:text="@string/bt_pick_clear_button"
android:layout_height="fill_parent" style="@style/spaced_buttons"
android:layout_width="wrap_content"
android:layout_weight="1"
/> />
</LinearLayout> </LinearLayout>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/list_group_default"
android:title="@string/list_group_default"
/>
<item android:id="@+id/list_group_rename"
android:title="@string/list_group_rename"
/>
<item android:id="@+id/list_group_moveup"
android:title="@string/list_group_moveup"
/>
<item android:id="@+id/list_group_movedown"
android:title="@string/list_group_movedown"
/>
<item android:id="@+id/list_group_delete"
android:title="@string/list_group_delete"
/>
</menu>

View file

@ -7,6 +7,9 @@
<item android:id="@+id/list_item_rename" <item android:id="@+id/list_item_rename"
android:title="@string/list_item_rename" android:title="@string/list_item_rename"
/> />
<item android:id="@+id/list_item_move"
android:title="@string/list_item_move"
/>
<item android:id="@+id/list_item_delete" <item android:id="@+id/list_item_delete"
android:title="@string/list_item_delete" android:title="@string/list_item_delete"
/> />

View file

@ -5,6 +5,10 @@
android:title="@string/button_new_game" android:title="@string/button_new_game"
android:icon="@android:drawable/ic_menu_add" android:icon="@android:drawable/ic_menu_add"
/> />
<item android:id="@+id/gamel_menu_newgroup"
android:title="@string/button_new_group"
android:icon="@android:drawable/ic_menu_add"
/>
<item android:id="@+id/gamel_menu_prefs" <item android:id="@+id/gamel_menu_prefs"
android:title="@string/menu_prefs" android:title="@string/menu_prefs"
android:icon="@android:drawable/ic_menu_preferences" android:icon="@android:drawable/ic_menu_preferences"
@ -23,10 +27,6 @@
android:icon="@android:drawable/ic_menu_send" android:icon="@android:drawable/ic_menu_send"
/> />
<item android:id="@+id/gamel_menu_delete_all"
android:title="@string/gamel_menu_delete_all"
android:icon="@android:drawable/ic_menu_delete"
/>
<item android:id="@+id/gamel_menu_checkmoves" <item android:id="@+id/gamel_menu_checkmoves"
android:title="@string/gamel_menu_checkmoves" android:title="@string/gamel_menu_checkmoves"
android:icon="@drawable/stat_notify_sync" android:icon="@drawable/stat_notify_sync"

View file

@ -9,7 +9,7 @@
<h3>New with this release</h3> <h3>New with this release</h3>
<ul> <ul>
<li>Allow grouping of games in collapsible user-defined categores: <li>Allow grouping of games in collapsible user-defined categories:
"Games with Kati", "Finished games", etc.</li> "Games with Kati", "Finished games", etc.</li>
</ul> </ul>

View file

@ -72,6 +72,7 @@
<string name="key_relay_regid">key_relay_regid</string> <string name="key_relay_regid">key_relay_regid</string>
<string name="key_checked_sms">key_checked_sms</string> <string name="key_checked_sms">key_checked_sms</string>
<string name="key_default_group">key_default_group</string> <string name="key_default_group">key_default_group</string>
<string name="key_group_posns">key_group_posns</string>
<string name="key_notagain_sync">key_notagain_sync</string> <string name="key_notagain_sync">key_notagain_sync</string>
<string name="key_notagain_chat">key_notagain_chat</string> <string name="key_notagain_chat">key_notagain_chat</string>

View file

@ -27,6 +27,7 @@
menuitem in main games-list screen's menu. (The botton can menuitem in main games-list screen's menu. (The botton can
be hidden in the same way as the above text.) --> be hidden in the same way as the above text.) -->
<string name="button_new_game">Add game</string> <string name="button_new_game">Add game</string>
<string name="button_new_group">Add group</string>
<!-- When the game list is empty and the above messages and button <!-- When the game list is empty and the above messages and button
are hidden via preferences, this text is shown --> are hidden via preferences, this text is shown -->
@ -142,6 +143,9 @@
<string name="list_item_config">Game settings...</string> <string name="list_item_config">Game settings...</string>
<!-- pulls up dialog to rename (change name of) the selected game --> <!-- pulls up dialog to rename (change name of) the selected game -->
<string name="list_item_rename">Rename...</string> <string name="list_item_rename">Rename...</string>
<!-- pulls up dialog to change the group of the selected game -->
<string name="list_item_move">Move to group...</string>
<!-- pulls up dialog to delete the selected game --> <!-- pulls up dialog to delete the selected game -->
<string name="list_item_delete">Delete</string> <string name="list_item_delete">Delete</string>
<!-- pulls up dialog to reset the selected game, that is to remove <!-- pulls up dialog to reset the selected game, that is to remove
@ -181,12 +185,6 @@
game? Resetting erases all moves and any connection game? Resetting erases all moves and any connection
information.</string> information.</string>
<!-- Text of confirmation dialog posted when gamel_menu_delete_all
button is pressed -->
<string name="confirm_delete_all">Are you sure you want to delete
all games?</string>
<!-- <!--
############################################################ ############################################################
# :Screens: # :Screens:
@ -630,7 +628,8 @@
<!-- Appended to the above in the phonies_warn case. User may <!-- Appended to the above in the phonies_warn case. User may
ignore the warning --> ignore the warning -->
<string name="badwords_accept"> Do you still want to accept this move?</string> <string name="badwords_accept">\u0020Do you still want to accept
this move?</string>
<!-- Appended to the above in the phonies_disallow case. User has <!-- Appended to the above in the phonies_disallow case. User has
lost his turn. --> lost his turn. -->
<string name="badwords_lost"> Turn lost.</string> <string name="badwords_lost"> Turn lost.</string>
@ -1235,7 +1234,7 @@
\u003cbr \\\u003E \u003cbr \\\u003E
\u003cbr \\\u003E \u003cbr \\\u003E
(full link: %1$s) (full link: %1$s )
</string> </string>
<!-- This is the body of the text version of the invitation. A URL <!-- This is the body of the text version of the invitation. A URL
@ -2089,7 +2088,7 @@
<!-- --> <!-- -->
<string name="new_app_availf">New version of %s</string> <string name="new_app_availf">New version of %s</string>
<!-- --> <!-- -->
<string name="new_app_avail">Tap to download</string> <string name="new_app_avail">Tap to download and install</string>
<!-- Used in formatting final scores display --> <!-- Used in formatting final scores display -->
<string name="str_resigned">Resigned</string> <string name="str_resigned">Resigned</string>
<!-- Used in formatting final scores display --> <!-- Used in formatting final scores display -->
@ -2130,9 +2129,32 @@
<string name="download_path_title">Downloads Directory</string> <string name="download_path_title">Downloads Directory</string>
<string name="newgroup_label">Name your new group:</string>
<string name="list_group_delete">Delete</string>
<string name="list_group_rename">Rename</string>
<string name="list_group_default">Put new games here</string>
<string name="list_group_moveup">Move up</string>
<string name="list_group_movedown">Move down</string>
<string name="group_cur_games">My games</string> <string name="group_cur_games">My games</string>
<string name="group_new_games">New games</string> <string name="group_new_games">New games</string>
<string name="group_confirm_del">Are you sure you want to delete
this group?</string>
<string name="group_confirm_delf">\u0020(It contains %d game[s],
which will also be deleted.)</string>
<string name="rename_group_label">Change the name of this group to:</string>
<string name="game_name_group_title">Name group</string>
<string name="cannot_delete_default_group">The group for new games
cannot be deleted."</string>
<string name="no_move_onegroup">Moving is impossible until there
is more than one group.</string>
<string name="group_namef">%1$s (%2$d games)</string>
<!-- Button shown in game over dialog triggering creation of new <!-- Button shown in game over dialog triggering creation of new
game with the same players and parameters as the one that game with the same players and parameters as the one that
just ended. --> just ended. -->
@ -2141,4 +2163,5 @@
<string name="square_tiles">Square rack tiles</string> <string name="square_tiles">Square rack tiles</string>
<string name="square_tiles_summary">Even if they can be taller</string> <string name="square_tiles_summary">Even if they can be taller</string>
<string name="change_groupf">Move game %s</string>
</resources> </resources>

View file

@ -60,5 +60,11 @@
<item name="android:layout_marginBottom">3sp</item> <item name="android:layout_marginBottom">3sp</item>
</style> </style>
<style name="spaced_buttons">
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item>
</style>
</resources> </resources>

View file

@ -22,6 +22,7 @@ package org.eehouse.android.xw4;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
@ -33,7 +34,7 @@ public class DBHelper extends SQLiteOpenHelper {
public static final String TABLE_NAME_DICTINFO = "dictinfo"; public static final String TABLE_NAME_DICTINFO = "dictinfo";
public static final String TABLE_NAME_GROUPS = "groups"; public static final String TABLE_NAME_GROUPS = "groups";
private static final String DB_NAME = "xwdb"; private static final String DB_NAME = "xwdb";
private static final int DB_VERSION = 15; private static final int DB_VERSION = 16;
public static final String GAME_NAME = "GAME_NAME"; public static final String GAME_NAME = "GAME_NAME";
public static final String NUM_MOVES = "NUM_MOVES"; public static final String NUM_MOVES = "NUM_MOVES";
@ -197,6 +198,8 @@ public class DBHelper extends SQLiteOpenHelper {
case 14: case 14:
addSumColumn( db, GROUPID ); addSumColumn( db, GROUPID );
createGroupsTable( db ); createGroupsTable( db );
case 15:
moveToCurGames( db );
// nothing yet // nothing yet
break; break;
default: default:
@ -260,4 +263,21 @@ public class DBHelper extends SQLiteOpenHelper {
XWPrefs.setDefaultNewGameGroup( m_context, newGroup ); XWPrefs.setDefaultNewGameGroup( m_context, newGroup );
} }
// Move all existing games to the row previously named "cur games'
private void moveToCurGames( SQLiteDatabase db )
{
String name = m_context.getString( R.string.group_cur_games );
String[] columns = { "rowid" };
String selection = String.format( "%s = '%s'", GROUPNAME, name );
Cursor cursor = db.query( DBHelper.TABLE_NAME_GROUPS, columns,
selection, null, null, null, null );
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
long rowid = cursor.getLong( cursor.getColumnIndex("rowid") );
ContentValues values = new ContentValues();
values.put( GROUPID, rowid );
db.update( DBHelper.TABLE_NAME_SUM, values, null, null );
}
cursor.close();
}
} }

View file

@ -320,6 +320,7 @@ public class DBUtils {
} }
db.close(); db.close();
notifyListeners( rowid, false ); notifyListeners( rowid, false );
invalGroupsCache();
} }
} // saveSummary } // saveSummary
@ -728,6 +729,7 @@ public class DBUtils {
if ( -1 != rowid ) { // Means new game? if ( -1 != rowid ) { // Means new game?
notifyListeners( rowid, false ); notifyListeners( rowid, false );
} }
invalGroupsCache();
return rowid; return rowid;
} }
@ -782,35 +784,6 @@ public class DBUtils {
notifyListeners( lock.getRowid(), true ); notifyListeners( lock.getRowid(), true );
} }
public static long[] gamesList( Context context )
{
long[] result;
synchronized( DBUtils.class ) {
if ( null == s_cachedRowIDs ) {
initDB( context );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getReadableDatabase();
String[] columns = { ROW_ID };
String orderBy = DBHelper.CREATE_TIME + " DESC";
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM,
columns, null, null, null,
null, orderBy );
int count = cursor.getCount();
s_cachedRowIDs = new long[count];
int index = cursor.getColumnIndex( ROW_ID );
for ( int ii = 0; cursor.moveToNext(); ++ii ) {
s_cachedRowIDs[ii] = cursor.getLong( index );
}
cursor.close();
db.close();
}
}
result = s_cachedRowIDs;
}
return result;
}
private static void clearRowIDsCache() private static void clearRowIDsCache()
{ {
synchronized( DBUtils.class ) { synchronized( DBUtils.class ) {
@ -865,6 +838,249 @@ public class DBUtils {
return result; return result;
} }
// Groups stuff
public static class GameGroupInfo {
public String m_name;
public boolean m_expanded;
public long m_lastMoveTime;
public boolean m_hasTurn;
public boolean m_turnLocal;
public GameGroupInfo( String name, boolean expanded ) {
m_name = name; m_expanded = expanded;
m_lastMoveTime = 0;
}
}
private static HashMap<Long,GameGroupInfo> s_groupsCache = null;
private static void invalGroupsCache()
{
s_groupsCache = null;
}
// Return map of string (group name) to info about all games in
// that group.
public static HashMap<Long,GameGroupInfo> getGroups( Context context )
{
if ( null == s_groupsCache ) {
HashMap<Long,GameGroupInfo> result =
new HashMap<Long,GameGroupInfo>();
initDB( context );
String[] columns = { ROW_ID, DBHelper.GROUPNAME,
DBHelper.EXPANDED };
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getReadableDatabase();
Cursor cursor = db.query( DBHelper.TABLE_NAME_GROUPS, columns,
null, // selection
null, // args
null, // groupBy
null, // having
null //orderby
);
int idIndex = cursor.getColumnIndex( ROW_ID );
int nameIndex = cursor.getColumnIndex( DBHelper.GROUPNAME );
int expandedIndex = cursor.getColumnIndex( DBHelper.EXPANDED );
while ( cursor.moveToNext() ) {
String name = cursor.getString( nameIndex );
long id = cursor.getLong( idIndex );
Assert.assertNotNull( name );
boolean expanded = 0 != cursor.getInt( expandedIndex );
result.put( id, new GameGroupInfo( name, expanded ) );
}
cursor.close();
Iterator<Long> iter = result.keySet().iterator();
while ( iter.hasNext() ) {
Long id = iter.next();
GameGroupInfo ggi = result.get( id );
readTurnInfo( db, id, ggi );
}
db.close();
}
s_groupsCache = result;
}
return s_groupsCache;
} // getGroups
private static void readTurnInfo( SQLiteDatabase db, long id,
GameGroupInfo ggi )
{
String[] columns = { DBHelper.LASTMOVE, DBHelper.GIFLAGS,
DBHelper.TURN };
String orderBy = DBHelper.LASTMOVE;
String selection = String.format( "%s=%d", DBHelper.GROUPID, id );
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
selection,
null, // args
null, // groupBy,
null, // having
orderBy
);
// We want the earliest LASTPLAY_TIME (i.e. the first we see
// since they're in order) that's a local turn, if any,
// otherwise a non-local turn.
long lastPlayTimeLocal = 0;
long lastPlayTimeRemote = 0;
int indexLPT = cursor.getColumnIndex( DBHelper.LASTMOVE );
int indexFlags = cursor.getColumnIndex( DBHelper.GIFLAGS );
int turnFlags = cursor.getColumnIndex( DBHelper.TURN );
while ( cursor.moveToNext() && 0 == lastPlayTimeLocal ) {
int flags = cursor.getInt( indexFlags );
int turn = cursor.getInt( turnFlags );
Boolean isLocal = GameSummary.localTurnNext( flags, turn );
if ( null != isLocal ) {
long lpt = cursor.getLong( indexLPT );
if ( isLocal ) {
lastPlayTimeLocal = lpt;
} else if ( 0 == lastPlayTimeRemote ) {
lastPlayTimeRemote = lpt;
}
}
}
cursor.close();
ggi.m_hasTurn = 0 != lastPlayTimeLocal || 0 != lastPlayTimeRemote;
if ( ggi.m_hasTurn ) {
ggi.m_turnLocal = 0 != lastPlayTimeLocal;
if ( ggi.m_turnLocal ) {
ggi.m_lastMoveTime = lastPlayTimeLocal;
} else {
ggi.m_lastMoveTime = lastPlayTimeRemote;
}
// DateFormat df = DateFormat.getDateTimeInstance( DateFormat.SHORT,
// DateFormat.SHORT );
// DbgUtils.logf( "using last play time %s for",
// df.format( new Date( 1000 * ggi.m_lastMoveTime ) ) );
}
}
public static long[] getGroupGames( Context context, long groupID )
{
long[] result = null;
initDB( context );
String[] columns = { ROW_ID };
String selection = String.format( "%s=%d", DBHelper.GROUPID, groupID );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getReadableDatabase();
String orderBy = DBHelper.CREATE_TIME + " DESC";
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
selection, // selection
null, // args
null, // groupBy
null, // having
orderBy
);
int index = cursor.getColumnIndex( ROW_ID );
result = new long[ cursor.getCount() ];
for ( int ii = 0; cursor.moveToNext(); ++ii ) {
long rowid = cursor.getInt( index );
result[ii] = rowid;
}
cursor.close();
db.close();
}
return result;
}
public static long getGroupForGame( Context context, long rowid )
{
long result = ROWID_NOTFOUND;
initDB( context );
String[] columns = { DBHelper.GROUPID };
String selection = String.format( ROW_ID_FMT, rowid );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getReadableDatabase();
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
selection, // selection
null, // args
null, // groupBy
null, // having
null //orderby
);
if ( cursor.moveToNext() ) {
int index = cursor.getColumnIndex( DBHelper.GROUPID );
result = cursor.getLong( index );
}
cursor.close();
db.close();
}
return result;
}
public static long addGroup( Context context, String name )
{
long rowid = ROWID_NOTFOUND;
if ( null != name && 0 < name.length() ) {
HashMap<Long,GameGroupInfo> gameInfo = getGroups( context );
if ( null == gameInfo.get( name ) ) {
ContentValues values = new ContentValues();
values.put( DBHelper.GROUPNAME, name );
values.put( DBHelper.EXPANDED, 0 );
initDB( context );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getWritableDatabase();
rowid = db.insert( DBHelper.TABLE_NAME_GROUPS, null,
values );
db.close();
}
invalGroupsCache();
}
}
return rowid;
}
public static void deleteGroup( Context context, long groupid )
{
initDB( context );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getWritableDatabase();
// Nuke games having this group id
String selection =
String.format( "%s=%d", DBHelper.GROUPID, groupid );
db.delete( DBHelper.TABLE_NAME_SUM, selection, null );
// And nuke the group record itself
selection = String.format( ROW_ID_FMT, groupid );
db.delete( DBHelper.TABLE_NAME_GROUPS, selection, null );
db.close();
}
invalGroupsCache();
}
public static void setGroupName( Context context, long groupid,
String name )
{
ContentValues values = new ContentValues();
values.put( DBHelper.GROUPNAME, name );
updateRow( context, DBHelper.TABLE_NAME_GROUPS, groupid, values );
invalGroupsCache();
}
public static void setGroupExpanded( Context context, long groupid,
boolean expanded )
{
ContentValues values = new ContentValues();
values.put( DBHelper.EXPANDED, expanded? 1 : 0 );
updateRow( context, DBHelper.TABLE_NAME_GROUPS, groupid, values );
invalGroupsCache();
}
// Change group id of a game
public static void moveGame( Context context, long gameid, long groupid )
{
ContentValues values = new ContentValues();
values.put( DBHelper.GROUPID, groupid );
updateRow( context, DBHelper.TABLE_NAME_SUM, gameid, values );
}
private static String getChatHistoryStr( Context context, long rowid ) private static String getChatHistoryStr( Context context, long rowid )
{ {
String result = null; String result = null;

View file

@ -23,7 +23,6 @@ package org.eehouse.android.xw4;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.app.ExpandableListActivity;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface; import android.content.DialogInterface;
@ -33,7 +32,6 @@ import android.content.res.Resources;
import android.database.DataSetObserver; import android.database.DataSetObserver;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu.ContextMenuInfo;
import android.view.ContextMenu; import android.view.ContextMenu;
@ -47,9 +45,7 @@ import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo; import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.ExpandableListView; import android.widget.ExpandableListView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import junit.framework.Assert; import junit.framework.Assert;
@ -58,7 +54,7 @@ import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.jni.JNIUtilsImpl; import org.eehouse.android.xw4.jni.JNIUtilsImpl;
import org.eehouse.android.xw4.DictUtils.DictLoc; import org.eehouse.android.xw4.DictUtils.DictLoc;
public class DictsActivity extends ExpandableListActivity public class DictsActivity extends XWExpandableListActivity
implements View.OnClickListener, XWListItem.DeleteCallback, implements View.OnClickListener, XWListItem.DeleteCallback,
MountEventReceiver.SDCardNotifiee, DlgDelegate.DlgClickNotify, MountEventReceiver.SDCardNotifiee, DlgDelegate.DlgClickNotify,
DictImportActivity.DownloadFinishedListener { DictImportActivity.DownloadFinishedListener {
@ -86,10 +82,8 @@ public class DictsActivity extends ExpandableListActivity
private String m_deleteDict = null; private String m_deleteDict = null;
private String m_download; private String m_download;
private ExpandableListView m_expView; private ExpandableListView m_expView;
private DlgDelegate m_delegate;
private String[] m_locNames; private String[] m_locNames;
private DictListAdapter m_adapter; private DictListAdapter m_adapter;
private Handler m_handler;
private long m_packedPosition; private long m_packedPosition;
private DictLoc m_moveFromLoc; private DictLoc m_moveFromLoc;
@ -338,7 +332,6 @@ public class DictsActivity extends ExpandableListActivity
int lang = intent.getIntExtra( MultiService.LANG, -1 ); int lang = intent.getIntExtra( MultiService.LANG, -1 );
String name = intent.getStringExtra( MultiService.DICT ); String name = intent.getStringExtra( MultiService.DICT );
m_launchedForMissing = true; m_launchedForMissing = true;
m_handler = new Handler();
DictImportActivity DictImportActivity
.downloadDictInBack( DictsActivity.this, lang, .downloadDictInBack( DictsActivity.this, lang,
name, DictsActivity.this ); name, DictsActivity.this );
@ -355,7 +348,7 @@ public class DictsActivity extends ExpandableListActivity
break; break;
default: default:
dialog = m_delegate.onCreateDialog( id ); dialog = super.onCreateDialog( id );
doRemove = false; doRemove = false;
break; break;
} }
@ -371,7 +364,6 @@ public class DictsActivity extends ExpandableListActivity
protected void onPrepareDialog( int id, Dialog dialog ) protected void onPrepareDialog( int id, Dialog dialog )
{ {
super.onPrepareDialog( id, dialog ); super.onPrepareDialog( id, dialog );
m_delegate.onPrepareDialog( id, dialog );
if ( MOVE_DICT == id ) { if ( MOVE_DICT == id ) {
// The move button should always start out disabled // The move button should always start out disabled
@ -399,7 +391,6 @@ public class DictsActivity extends ExpandableListActivity
Resources res = getResources(); Resources res = getResources();
m_locNames = res.getStringArray( R.array.loc_names ); m_locNames = res.getStringArray( R.array.loc_names );
m_delegate = new DlgDelegate( this, this, savedInstanceState );
m_factory = LayoutInflater.from( this ); m_factory = LayoutInflater.from( this );
m_download = getString( R.string.download_dicts ); m_download = getString( R.string.download_dicts );
@ -447,7 +438,6 @@ public class DictsActivity extends ExpandableListActivity
protected void onSaveInstanceState( Bundle outState ) protected void onSaveInstanceState( Bundle outState )
{ {
super.onSaveInstanceState( outState ); super.onSaveInstanceState( outState );
m_delegate.onSaveInstanceState( outState );
outState.putLong( PACKED_POSITION, m_packedPosition ); outState.putLong( PACKED_POSITION, m_packedPosition );
outState.putString( NAME, m_name ); outState.putString( NAME, m_name );
@ -622,8 +612,7 @@ public class DictsActivity extends ExpandableListActivity
} }
} }
m_delegate.showConfirmThen( msg, R.string.button_delete, showConfirmThen( msg, R.string.button_delete, DELETE_DICT_ACTION );
DELETE_DICT_ACTION );
} }
// MountEventReceiver.SDCardNotifiee interface // MountEventReceiver.SDCardNotifiee interface
@ -632,7 +621,7 @@ public class DictsActivity extends ExpandableListActivity
DbgUtils.logf( "DictsActivity.cardMounted(%b)", nowMounted ); DbgUtils.logf( "DictsActivity.cardMounted(%b)", nowMounted );
// post so other SDCardNotifiee implementations get a chance // post so other SDCardNotifiee implementations get a chance
// to process first: avoid race conditions // to process first: avoid race conditions
new Handler().post( new Runnable() { post( new Runnable() {
public void run() { public void run() {
mkListAdapter(); mkListAdapter();
expandGroups(); expandGroups();
@ -773,7 +762,7 @@ public class DictsActivity extends ExpandableListActivity
public void downloadFinished( String name, final boolean success ) public void downloadFinished( String name, final boolean success )
{ {
if ( m_launchedForMissing ) { if ( m_launchedForMissing ) {
m_handler.post( new Runnable() { post( new Runnable() {
public void run() { public void run() {
if ( success ) { if ( success ) {
Intent intent = getIntent(); Intent intent = getIntent();

View file

@ -79,6 +79,7 @@ public class ExpiringDelegate {
if ( haveTurnLocal ) { if ( haveTurnLocal ) {
setBackground(); setBackground();
} else { } else {
m_view.setBackgroundDrawable( null );
m_view.setWillNotDraw( false ); m_view.setWillNotDraw( false );
} }
} }

View file

@ -41,7 +41,17 @@ class ExpiringTextView extends TextView {
if ( null == m_delegate ) { if ( null == m_delegate ) {
m_delegate = new ExpiringDelegate( m_context, this, handler ); m_delegate = new ExpiringDelegate( m_context, this, handler );
} }
m_delegate.configure( haveTurn, haveTurnLocal, startSecs ); setPct( haveTurn, haveTurnLocal, startSecs );
}
public void setPct( boolean haveTurn, boolean haveTurnLocal,
long startSecs )
{
if ( null != m_delegate ) {
m_delegate.configure( haveTurn, haveTurnLocal, startSecs );
} else {
DbgUtils.logf( "m_delegate null; skipping" );
}
} }
@Override @Override

View file

@ -20,93 +20,321 @@
package org.eehouse.android.xw4; package org.eehouse.android.xw4;
import android.content.Context; import android.content.Context;
import android.database.DataSetObserver;
import android.os.Handler; import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ListAdapter; import android.widget.ExpandableListAdapter;
import android.widget.ListView; import android.widget.ExpandableListView;
import java.util.HashMap; // class is not synchronized
import java.util.HashSet;
import java.util.Set;
import java.util.Iterator;
import junit.framework.Assert; import junit.framework.Assert;
import org.eehouse.android.xw4.jni.*; import org.eehouse.android.xw4.jni.*;
import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole; import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.DBUtils.GameGroupInfo;
public class GameListAdapter extends XWListAdapter { public class GameListAdapter implements ExpandableListAdapter {
private Context m_context; private Context m_context;
private ListView m_list; private ExpandableListView m_list;
private LayoutInflater m_factory;
private int m_fieldID; private int m_fieldID;
private Handler m_handler; private Handler m_handler;
private LoadItemCB m_cb; private LoadItemCB m_cb;
private long[] m_positions;
public interface LoadItemCB { public interface LoadItemCB {
public void itemClicked( long rowid, GameSummary summary ); public void itemClicked( long rowid, GameSummary summary );
} }
public GameListAdapter( Context context, ListView list, public GameListAdapter( Context context, ExpandableListView list,
Handler handler, LoadItemCB cb, String fieldName ) { Handler handler, LoadItemCB cb, long[] positions,
super( DBUtils.gamesList(context).length ); String fieldName )
{
m_context = context; m_context = context;
m_list = list; m_list = list;
m_handler = handler; m_handler = handler;
m_cb = cb; m_cb = cb;
m_factory = LayoutInflater.from( context ); m_positions = checkPositions( positions );
m_fieldID = fieldToID( fieldName ); m_fieldID = fieldToID( fieldName );
} }
@Override public long[] getPositions()
public int getCount() { {
return DBUtils.gamesList(m_context).length; Set<Long> keys = gameInfo().keySet(); // do not modify!!!!
if ( null == m_positions || m_positions.length != keys.size() ) {
HashSet<Long> unused = new HashSet<Long>( keys );
long[] newArray = new long[unused.size()];
// First copy the existing values, in order
int nextIndx = 0;
if ( null != m_positions ) {
for ( long id: m_positions ) {
if ( unused.contains( id ) ) {
newArray[nextIndx++] = id;
unused.remove( id );
}
}
}
// Then copy in what's left
Iterator<Long> iter = unused.iterator();
while ( iter.hasNext() ) {
newArray[nextIndx++] = iter.next();
}
m_positions = newArray;
}
return m_positions;
} }
// Views. A view depends on a summary, which takes time to load. public boolean moveGroup( long groupid, int moveBy )
// When one needs loading it's done via an async task.
public View getView( int position, View convertView, ViewGroup parent )
{ {
GameListItem result = (GameListItem) int src = getGroupPosition( groupid );
m_factory.inflate( R.layout.game_list_item, null ); int dest = src + moveBy;
result.init( m_handler, DBUtils.gamesList(m_context)[position], long[] positions = getPositions();
m_fieldID, m_cb ); boolean success = 0 <= dest && dest < positions.length;
if ( success ) {
long tmp = positions[src];
positions[src] = positions[dest];
positions[dest] = tmp;
}
return success;
}
public void expandGroups( ExpandableListView view )
{
HashMap<Long,GameGroupInfo> info = gameInfo();
for ( int ii = 0; ii < info.size(); ++ii ) {
GameGroupInfo ggi = getInfoForGroup( ii );
if ( ggi.m_expanded ) {
view.expandGroup( ii );
}
}
}
public long getRowIDFor( int group, int child )
{
long rowid = DBUtils.ROWID_NOTFOUND;
long[] rows = getRows( getPositions()[group] );
if ( child < rows.length ) {
rowid = rows[child];
}
return rowid;
}
public long getRowIDFor( long packedPosition )
{
int childPosition = ExpandableListView.
getPackedPositionChild( packedPosition );
int groupPosition = ExpandableListView.
getPackedPositionGroup( packedPosition );
return getRowIDFor( groupPosition, childPosition );
}
public long getGroupIDFor( int groupPos )
{
long id = getPositions()[groupPos];
return id;
}
public String groupName( long groupid )
{
HashMap<Long,GameGroupInfo> info = gameInfo();
GameGroupInfo ggi = info.get( groupid );
return ggi.m_name;
}
//////////////////////////////////////////////////////////////////////////
// ExpandableListAdapter interface
//////////////////////////////////////////////////////////////////////////
public long getCombinedGroupId( long groupId )
{
return groupId;
}
public long getCombinedChildId( long groupId, long childId )
{
return groupId << 16 | childId;
}
public boolean isEmpty() { return false; }
public void onGroupCollapsed( int groupPosition )
{
long groupid = getGroupIDFor( groupPosition );
DBUtils.setGroupExpanded( m_context, groupid, false );
}
public void onGroupExpanded( int groupPosition )
{
long groupid = getGroupIDFor( groupPosition );
DBUtils.setGroupExpanded( m_context, groupid, true );
}
public boolean areAllItemsEnabled() { return true; }
public boolean isChildSelectable( int groupPosition, int childPosition )
{ return true; }
public View getChildView( int groupPosition, int childPosition,
boolean isLastChild, View convertView,
ViewGroup parent)
{
View result = null;
if ( null != convertView ) {
// DbgUtils.logf( "getChildView gave non-null convertView" );
if ( convertView instanceof GameListItem ) {
GameListItem child = (GameListItem)convertView;
long rowid = getRowIDFor( groupPosition, childPosition );
if ( child.getRowID() == rowid ) {
DbgUtils.logf( "reusing child for rowid %d", rowid );
result = child;
}
}
}
if ( null == result ) {
result = getChildView( groupPosition, childPosition );
}
return result; return result;
} }
private View getChildView( int groupPosition, int childPosition )
{
long rowid = getRowIDFor( groupPosition, childPosition );
GameListItem result =
GameListItem.makeForRow( m_context, rowid, m_handler,
groupPosition, m_fieldID, m_cb );
return result;
}
public View getGroupView( int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent )
{
// if ( null != convertView ) {
// DbgUtils.logf( "getGroupView gave non-null convertView" );
// }
GameListGroup view = (GameListGroup)
Utils.inflate(m_context, R.layout.game_list_group );
view.setGroupPosition( groupPosition );
if ( !isExpanded ) {
GameGroupInfo ggi = getInfoForGroup( groupPosition );
view.setPct( m_handler, ggi.m_hasTurn, ggi.m_turnLocal,
ggi.m_lastMoveTime );
}
int nKids = getChildrenCount( groupPosition );
String name = m_context.getString( R.string.group_namef,
groupNames()[groupPosition], nKids );
view.setText( name );
return view;
}
public boolean hasStableIds() { return false; }
public long getChildId( int groupPosition, int childPosition )
{
return childPosition;
}
public long getGroupId( int groupPosition )
{
return groupPosition;
}
public Object getChild( int groupPosition, int childPosition )
{
return null;
}
public Object getGroup( int groupPosition )
{
return null;
}
public int getChildrenCount( int groupPosition )
{
long[] rows = getRows( getPositions()[groupPosition] );
return rows.length;
}
public int getGroupCount()
{
return gameInfo().size();
}
public void registerDataSetObserver( DataSetObserver obs ){}
public void unregisterDataSetObserver( DataSetObserver obs ){}
public void inval( long rowid ) public void inval( long rowid )
{ {
GameListItem child = getItemFor( rowid ); GameListItem child = getGameItemFor( rowid );
int groupPosition;
if ( null != child && child.getRowID() == rowid ) { if ( null != child && child.getRowID() == rowid ) {
child.forceReload(); child.forceReload();
groupPosition = child.getGroupPosition();
} else { } else {
DbgUtils.logf( "no child for rowid %d", rowid ); // DbgUtils.logf( "no child for rowid %d", rowid );
GameListItem.inval( rowid ); GameListItem.inval( rowid );
m_list.invalidate(); m_list.invalidate();
long groupID = DBUtils.getGroupForGame( m_context, rowid );
groupPosition = getGroupPosition( groupID );
} }
reloadGroup( groupPosition );
} }
public void invalName( long rowid ) public void invalName( long rowid )
{ {
GameListItem item = getItemFor( rowid ); GameListItem item = getGameItemFor( rowid );
if ( null != item ) { if ( null != item ) {
item.invalName(); item.invalName();
} }
} }
private long[] getRows( long groupID )
{
return DBUtils.getGroupGames( m_context, groupID );
}
public String[] groupNames()
{
HashMap<Long,GameGroupInfo> info = gameInfo();
long[] positions = getPositions();
String[] names = new String[ positions.length ];
for ( int ii = 0; ii < names.length; ++ii ) {
names[ii] = info.get(positions[ii]).m_name;
}
return names;
}
public int getGroupPosition( long groupid )
{
int result = -1;
long[] positions = getPositions();
for ( int pos = 0; pos < positions.length; ++pos ) {
if ( positions[pos] == groupid ) {
result = pos;
break;
}
}
return result;
}
public boolean setField( String fieldName ) public boolean setField( String fieldName )
{ {
boolean changed = false; boolean changed = false;
int newID = fieldToID( fieldName ); int newID = fieldToID( fieldName );
if ( -1 == newID ) { if ( -1 == newID ) {
if ( XWApp.DEBUG ) { DbgUtils.logf( "GameListAdapter.setField(): unable to match"
DbgUtils.logf( "GameListAdapter.setField(): unable to match" + " fieldName %s", fieldName );
+ " fieldName %s", fieldName );
}
} else if ( m_fieldID != newID ) { } else if ( m_fieldID != newID ) {
if ( XWApp.DEBUG ) {
DbgUtils.logf( "setField: clearing views cache for change"
+ " from %d to %d", m_fieldID, newID );
}
m_fieldID = newID; m_fieldID = newID;
// return true so caller will do onContentChanged. // return true so caller will do onContentChanged.
// There's no other way to signal GameListItem instances // There's no other way to signal GameListItem instances
@ -116,12 +344,41 @@ public class GameListAdapter extends XWListAdapter {
return changed; return changed;
} }
private GameListItem getItemFor( long rowid ) private GameGroupInfo getInfoForGroup( int groupPosition )
{
return gameInfo().get( getPositions()[groupPosition] );
}
private GameListItem getGameItemFor( long rowid )
{ {
GameListItem result = null; GameListItem result = null;
int position = positionFor( rowid ); int count = m_list.getChildCount();
if ( 0 <= position ) { for ( int ii = 0; ii < count; ++ii ) {
result = (GameListItem)m_list.getChildAt( position ); View view = m_list.getChildAt( ii );
if ( view instanceof GameListItem ) {
GameListItem tryme = (GameListItem)view;
if ( tryme.getRowID() == rowid ) {
result = tryme;
break;
}
}
}
return result;
}
private GameListGroup getGroupItemFor( int groupPosition )
{
GameListGroup result = null;
int count = m_list.getChildCount();
for ( int ii = 0; ii < count; ++ii ) {
View view = m_list.getChildAt( ii );
if ( view instanceof GameListGroup ) {
GameListGroup tryme = (GameListGroup)view;
if ( tryme.getGroupPosition() == groupPosition ) {
result = tryme;
break;
}
}
} }
return result; return result;
} }
@ -144,16 +401,32 @@ public class GameListAdapter extends XWListAdapter {
return result; return result;
} }
private int positionFor( long rowid ) private void reloadGroup( int groupPosition )
{ {
int position = -1; GameListGroup group = getGroupItemFor( groupPosition );
long[] rowids = DBUtils.gamesList( m_context ); if ( null != group ) {
for ( int ii = 0; ii < rowids.length; ++ii ) { GameGroupInfo ggi = getInfoForGroup( groupPosition );
if ( rowids[ii] == rowid ) { group.setPct( ggi.m_hasTurn, ggi.m_turnLocal, ggi.m_lastMoveTime );
position = ii; }
break; }
private HashMap<Long,GameGroupInfo> gameInfo()
{
return DBUtils.getGroups( m_context );
}
private long[] checkPositions( long[] positions )
{
long[] result = positions;
if ( null != positions ) {
Set<Long> posns = gameInfo().keySet();
for ( long id : positions ) {
if ( ! posns.contains( id ) ) {
result = null;
break;
}
} }
} }
return position; return result;
} }
} }

View file

@ -0,0 +1,46 @@
/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */
/*
* Copyright 2012 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.eehouse.android.xw4;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import org.eehouse.android.xw4.DBUtils.GameGroupInfo;
public class GameListGroup extends ExpiringTextView {
private int m_groupPosition;
public GameListGroup( Context cx, AttributeSet as )
{
super( cx, as );
}
public void setGroupPosition( int groupPosition )
{
m_groupPosition = groupPosition;
}
public int getGroupPosition()
{
return m_groupPosition;
}
}

View file

@ -57,6 +57,7 @@ public class GameListItem extends LinearLayout
private GameListAdapter.LoadItemCB m_cb; private GameListAdapter.LoadItemCB m_cb;
private int m_fieldID; private int m_fieldID;
private int m_loadingCount; private int m_loadingCount;
private int m_groupPosition;
public GameListItem( Context cx, AttributeSet as ) public GameListItem( Context cx, AttributeSet as )
{ {
@ -68,11 +69,12 @@ public class GameListItem extends LinearLayout
m_loadingCount = 0; m_loadingCount = 0;
} }
public void init( Handler handler, long rowid, int fieldID, private void init( Handler handler, long rowid, int groupPosition,
GameListAdapter.LoadItemCB cb ) int fieldID, GameListAdapter.LoadItemCB cb )
{ {
m_handler = handler; m_handler = handler;
m_rowid = rowid; m_rowid = rowid;
m_groupPosition = groupPosition;
m_fieldID = fieldID; m_fieldID = fieldID;
m_cb = cb; m_cb = cb;
@ -128,6 +130,11 @@ public class GameListItem extends LinearLayout
return m_rowid; return m_rowid;
} }
public int getGroupPosition()
{
return m_groupPosition;
}
// View.OnClickListener interface // View.OnClickListener interface
public void onClick( View view ) { public void onClick( View view ) {
m_expanded = !m_expanded; m_expanded = !m_expanded;
@ -297,6 +304,17 @@ public class GameListItem extends LinearLayout
} }
} // class LoadItemTask } // class LoadItemTask
public static GameListItem makeForRow( Context context, long rowid,
Handler handler, int groupPosition,
int fieldID,
GameListAdapter.LoadItemCB cb )
{
GameListItem result =
(GameListItem)Utils.inflate( context, R.layout.game_list_item );
result.init( handler, rowid, groupPosition, fieldID, cb );
return result;
}
public static void inval( long rowid ) public static void inval( long rowid )
{ {
synchronized( s_invalRows ) { synchronized( s_invalRows ) {

View file

@ -190,10 +190,10 @@ public class GameUtils {
return rowid; return rowid;
} }
public static void deleteGame( Context context, long rowid, public static boolean deleteGame( Context context, long rowid,
boolean informNow ) boolean informNow )
{ {
DbgUtils.logf( "deleteGame(rowid=%d)", rowid ); boolean success;
// does this need to be synchronized? // does this need to be synchronized?
GameLock lock = new GameLock( rowid, true ); GameLock lock = new GameLock( rowid, true );
if ( lock.tryLock() ) { if ( lock.tryLock() ) {
@ -201,8 +201,25 @@ public class GameUtils {
Utils.cancelNotification( context, (int)rowid ); Utils.cancelNotification( context, (int)rowid );
DBUtils.deleteGame( context, lock ); DBUtils.deleteGame( context, lock );
lock.unlock(); lock.unlock();
success = true;
} else { } else {
DbgUtils.logf( "deleteGame: unable to delete rowid %d", rowid ); DbgUtils.logf( "deleteGame: unable to delete rowid %d", rowid );
success = false;
}
return success;
}
public static void deleteGroup( Context context, long groupid )
{
int nSuccesses = 0;
long[] rowids = DBUtils.getGroupGames( context, groupid );
for ( int ii = rowids.length - 1; ii >= 0; --ii ) {
if ( deleteGame( context, rowids[ii], ii == 0 ) ) {
++nSuccesses;
}
}
if ( rowids.length == nSuccesses ) {
DBUtils.deleteGroup( context, groupid );
} }
} }
@ -499,13 +516,6 @@ public class GameUtils {
return allHere; return allHere;
} }
public static boolean gameDictsHere( Context context, int indx,
String[][] name, int[] lang )
{
long rowid = DBUtils.gamesList( context )[indx];
return gameDictsHere( context, rowid, name, lang );
}
public static String newName( Context context ) public static String newName( Context context )
{ {
return "untitled"; return "untitled";

View file

@ -41,29 +41,36 @@ import android.view.View;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.ExpandableListView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ListView; import android.widget.ListView;
import java.io.File; import java.io.File;
import java.util.Date; import java.util.Date;
// import android.telephony.PhoneStateListener; // import android.telephony.PhoneStateListener;
// import android.telephony.TelephonyManager; // import android.telephony.TelephonyManager;
import junit.framework.Assert; import junit.framework.Assert;
import org.eehouse.android.xw4.jni.*; import org.eehouse.android.xw4.jni.*;
public class GamesList extends XWListActivity public class GamesList extends XWExpandableListActivity
implements DBUtils.DBChangeListener, implements DBUtils.DBChangeListener,
GameListAdapter.LoadItemCB, GameListAdapter.LoadItemCB,
DictImportActivity.DownloadFinishedListener { DictImportActivity.DownloadFinishedListener {
private static final int WARN_NODICT = DlgDelegate.DIALOG_LAST + 1; private static final int WARN_NODICT = DlgDelegate.DIALOG_LAST + 1;
private static final int WARN_NODICT_SUBST = WARN_NODICT + 1; private static final int WARN_NODICT_SUBST = WARN_NODICT + 1;
private static final int WARN_NODICT_NEW = WARN_NODICT + 2; private static final int SHOW_SUBST = WARN_NODICT + 2;
private static final int SHOW_SUBST = WARN_NODICT + 3; private static final int GET_NAME = WARN_NODICT + 3;
private static final int GET_NAME = WARN_NODICT + 4; private static final int RENAME_GAME = WARN_NODICT + 4;
private static final int RENAME_GAME = WARN_NODICT + 5; private static final int NEW_GROUP = WARN_NODICT + 5;
private static final int RENAME_GROUP = WARN_NODICT + 6;
private static final int CHANGE_GROUP = WARN_NODICT + 7;
private static final int WARN_NODICT_NEW = WARN_NODICT + 8;
private static final String SAVE_ROWID = "SAVE_ROWID"; private static final String SAVE_ROWID = "SAVE_ROWID";
private static final String SAVE_GROUPID = "SAVE_GROUPID";
private static final String SAVE_DICTNAMES = "SAVE_DICTNAMES"; private static final String SAVE_DICTNAMES = "SAVE_DICTNAMES";
private static final String RELAYIDS_EXTRA = "relayids"; private static final String RELAYIDS_EXTRA = "relayids";
@ -73,9 +80,9 @@ public class GamesList extends XWListActivity
private static final int NEW_NET_GAME_ACTION = 1; private static final int NEW_NET_GAME_ACTION = 1;
private static final int RESET_GAME_ACTION = 2; private static final int RESET_GAME_ACTION = 2;
private static final int DELETE_GAME_ACTION = 3; private static final int DELETE_GAME_ACTION = 3;
private static final int DELETE_ALL_ACTION = 4; private static final int SYNC_MENU_ACTION = 4;
private static final int SYNC_MENU_ACTION = 5; private static final int NEW_FROM_ACTION = 5;
private static final int NEW_FROM_ACTION = 6; private static final int DELETE_GROUP_ACTION = 6;
private static final int[] DEBUGITEMS = { R.id.gamel_menu_loaddb private static final int[] DEBUGITEMS = { R.id.gamel_menu_loaddb
, R.id.gamel_menu_storedb , R.id.gamel_menu_storedb
, R.id.gamel_menu_checkupdates , R.id.gamel_menu_checkupdates
@ -90,12 +97,16 @@ public class GamesList extends XWListActivity
private String[] m_sameLangDicts; private String[] m_sameLangDicts;
private int m_missingDictLang; private int m_missingDictLang;
private long m_rowid; private long m_rowid;
private long m_groupid;
private String m_nameField;
private NetLaunchInfo m_netLaunchInfo; private NetLaunchInfo m_netLaunchInfo;
private GameNamer m_namer;
@Override @Override
protected Dialog onCreateDialog( int id ) protected Dialog onCreateDialog( int id )
{ {
DialogInterface.OnClickListener lstnr; DialogInterface.OnClickListener lstnr;
DialogInterface.OnClickListener lstnr2;
LinearLayout layout; LinearLayout layout;
Dialog dialog = super.onCreateDialog( id ); Dialog dialog = super.onCreateDialog( id );
@ -178,36 +189,87 @@ public class GamesList extends XWListActivity
.setNegativeButton( R.string.button_cancel, null ) .setNegativeButton( R.string.button_cancel, null )
.setSingleChoiceItems( m_sameLangDicts, 0, null ) .setSingleChoiceItems( m_sameLangDicts, 0, null )
.create(); .create();
;
// Force destruction so onCreateDialog() will get // Force destruction so onCreateDialog() will get
// called next time and we can insert a different // called next time and we can insert a different
// list. There seems to be no way to change the list // list. There seems to be no way to change the list
// inside onPrepareDialog(). // inside onPrepareDialog().
dialog.setOnDismissListener(new DialogInterface. Utils.setRemoveOnDismiss( this, dialog, id );
OnDismissListener() {
public void onDismiss(DialogInterface dlg) {
removeDialog( SHOW_SUBST );
}
});
break; break;
case RENAME_GAME: case RENAME_GAME:
final GameNamer namerView =
(GameNamer)Utils.inflate( this, R.layout.rename_game );
namerView.setName( GameUtils.getName( this, m_rowid ) );
namerView.setLabel( R.string.rename_label );
lstnr = new DialogInterface.OnClickListener() { lstnr = new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlg, int item ) { public void onClick( DialogInterface dlg, int item ) {
String name = namerView.getName(); String name = m_namer.getName();
DBUtils.setName( GamesList.this, m_rowid, name ); DBUtils.setName( GamesList.this, m_rowid, name );
m_adapter.invalName( m_rowid ); m_adapter.invalName( m_rowid );
} }
}; };
dialog = buildNamerDlg( GameUtils.getName( this, m_rowid ),
R.string.rename_label,
R.string.game_rename_title,
lstnr, RENAME_GAME );
break;
case RENAME_GROUP:
lstnr = new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlg, int item ) {
String name = m_namer.getName();
DBUtils.setGroupName( GamesList.this, m_groupid,
name );
m_adapter.inval( m_rowid );
onContentChanged();
}
};
dialog = buildNamerDlg( m_adapter.groupName( m_groupid ),
R.string.rename_group_label,
R.string.game_name_group_title,
lstnr, RENAME_GROUP );
break;
case NEW_GROUP:
lstnr = new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlg, int item ) {
String name = m_namer.getName();
DBUtils.addGroup( GamesList.this, name );
// m_adapter.inval();
onContentChanged();
}
};
dialog = buildNamerDlg( "", R.string.newgroup_label,
R.string.game_name_group_title,
lstnr, RENAME_GROUP );
Utils.setRemoveOnDismiss( this, dialog, id );
break;
case CHANGE_GROUP:
final long startGroup = DBUtils.getGroupForGame( this, m_rowid );
final int[] selItem = {-1}; // hack!!!!
lstnr = new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlgi, int item ) {
selItem[0] = item;
AlertDialog dlg = (AlertDialog)dlgi;
Button btn =
dlg.getButton( AlertDialog.BUTTON_POSITIVE );
long newGroup = m_adapter.getGroupIDFor( item );
btn.setEnabled( newGroup != startGroup );
}
};
lstnr2 = new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlg, int item ) {
Assert.assertTrue( -1 != selItem[0] );
long gid = m_adapter.getGroupIDFor( selItem[0] );
DBUtils.moveGame( GamesList.this, m_rowid, gid );
onContentChanged();
}
};
String[] groups = m_adapter.groupNames();
int curGroupPos = m_adapter.getGroupPosition( startGroup );
String name = GameUtils.getName( this, m_rowid );
dialog = new AlertDialog.Builder( this ) dialog = new AlertDialog.Builder( this )
.setTitle( R.string.game_rename_title ) .setTitle( getString( R.string.change_groupf, name ) )
.setSingleChoiceItems( groups, curGroupPos, lstnr )
.setPositiveButton( R.string.button_move, lstnr2 )
.setNegativeButton( R.string.button_cancel, null ) .setNegativeButton( R.string.button_cancel, null )
.setPositiveButton( R.string.button_ok, lstnr )
.setView( namerView )
.create(); .create();
Utils.setRemoveOnDismiss( this, dialog, id ); Utils.setRemoveOnDismiss( this, dialog, id );
break; break;
@ -248,6 +310,16 @@ public class GamesList extends XWListActivity
return dialog; return dialog;
} // onCreateDialog } // onCreateDialog
@Override protected void onPrepareDialog( int id, Dialog dialog )
{
super.onPrepareDialog( id, dialog );
if ( CHANGE_GROUP == id ) {
((AlertDialog)dialog).getButton( AlertDialog.BUTTON_POSITIVE )
.setEnabled( false );
}
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState)
{ {
@ -258,7 +330,7 @@ public class GamesList extends XWListActivity
getBundledData( savedInstanceState ); getBundledData( savedInstanceState );
setContentView(R.layout.game_list); setContentView(R.layout.game_list);
registerForContextMenu( getListView() ); registerForContextMenu( getExpandableListView() );
DBUtils.setDBChangeListener( this ); DBUtils.setDBChangeListener( this );
boolean isUpgrade = Utils.firstBootThisVersion( this ); boolean isUpgrade = Utils.firstBootThisVersion( this );
@ -280,11 +352,21 @@ public class GamesList extends XWListActivity
// R.string.key_notagain_newgame ); // R.string.key_notagain_newgame );
} }
}); });
newGameB = (Button)findViewById(R.id.new_group);
newGameB.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick( View v ) {
showDialog( NEW_GROUP );
}
});
String field = CommonPrefs.getSummaryField( this ); String field = CommonPrefs.getSummaryField( this );
m_adapter = new GameListAdapter( this, getListView(), new Handler(), long[] positions = XWPrefs.getGroupPositions( this );
this, field ); m_adapter = new GameListAdapter( this, getExpandableListView(),
new Handler(), this, positions,
field );
setListAdapter( m_adapter ); setListAdapter( m_adapter );
m_adapter.expandGroups( getExpandableListView() );
NetUtils.informOfDeaths( this ); NetUtils.informOfDeaths( this );
@ -318,13 +400,13 @@ public class GamesList extends XWListActivity
boolean hide = CommonPrefs.getHideIntro( this ); boolean hide = CommonPrefs.getHideIntro( this );
int hereOrGone = hide ? View.GONE : View.VISIBLE; int hereOrGone = hide ? View.GONE : View.VISIBLE;
for ( int id : new int[]{ R.id.empty_games_list, for ( int id : new int[]{ R.id.empty_games_list,
R.id.new_game } ) { R.id.new_buttons } ) {
View view = findViewById( id ); View view = findViewById( id );
view.setVisibility( hereOrGone ); view.setVisibility( hereOrGone );
} }
View empty = findViewById( R.id.empty_list_msg ); View empty = findViewById( R.id.empty_list_msg );
empty.setVisibility( hide ? View.VISIBLE : View.GONE ); empty.setVisibility( hide ? View.VISIBLE : View.GONE );
getListView().setEmptyView( hide? empty : null ); getExpandableListView().setEmptyView( hide? empty : null );
// TelephonyManager mgr = // TelephonyManager mgr =
// (TelephonyManager)getSystemService( Context.TELEPHONY_SERVICE ); // (TelephonyManager)getSystemService( Context.TELEPHONY_SERVICE );
@ -340,7 +422,8 @@ public class GamesList extends XWListActivity
// (TelephonyManager)getSystemService( Context.TELEPHONY_SERVICE ); // (TelephonyManager)getSystemService( Context.TELEPHONY_SERVICE );
// mgr.listen( m_phoneStateListener, PhoneStateListener.LISTEN_NONE ); // mgr.listen( m_phoneStateListener, PhoneStateListener.LISTEN_NONE );
// m_phoneStateListener = null; // m_phoneStateListener = null;
long[] positions = m_adapter.getPositions();
XWPrefs.setGroupPositions( this, positions );
super.onStop(); super.onStop();
} }
@ -356,6 +439,7 @@ public class GamesList extends XWListActivity
{ {
super.onSaveInstanceState( outState ); super.onSaveInstanceState( outState );
outState.putLong( SAVE_ROWID, m_rowid ); outState.putLong( SAVE_ROWID, m_rowid );
outState.putLong( SAVE_GROUPID, m_groupid );
outState.putString( SAVE_DICTNAMES, m_missingDictName ); outState.putString( SAVE_DICTNAMES, m_missingDictName );
if ( null != m_netLaunchInfo ) { if ( null != m_netLaunchInfo ) {
m_netLaunchInfo.putSelf( outState ); m_netLaunchInfo.putSelf( outState );
@ -366,6 +450,7 @@ public class GamesList extends XWListActivity
{ {
if ( null != bundle ) { if ( null != bundle ) {
m_rowid = bundle.getLong( SAVE_ROWID ); m_rowid = bundle.getLong( SAVE_ROWID );
m_groupid = bundle.getLong( SAVE_GROUPID );
m_netLaunchInfo = new NetLaunchInfo( bundle ); m_netLaunchInfo = new NetLaunchInfo( bundle );
m_missingDictName = bundle.getString( SAVE_DICTNAMES ); m_missingDictName = bundle.getString( SAVE_DICTNAMES );
} }
@ -457,12 +542,6 @@ public class GamesList extends XWListActivity
case DELETE_GAME_ACTION: case DELETE_GAME_ACTION:
GameUtils.deleteGame( this, m_rowid, true ); GameUtils.deleteGame( this, m_rowid, true );
break; break;
case DELETE_ALL_ACTION:
long[] games = DBUtils.gamesList( this );
for ( int ii = games.length - 1; ii >= 0; --ii ) {
GameUtils.deleteGame( this, games[ii], ii == 0 );
}
break;
case SYNC_MENU_ACTION: case SYNC_MENU_ACTION:
doSyncMenuitem(); doSyncMenuitem();
break; break;
@ -472,40 +551,88 @@ public class GamesList extends XWListActivity
m_adapter.inval( newid ); m_adapter.inval( newid );
} }
break; break;
case DELETE_GROUP_ACTION:
GameUtils.deleteGroup( this, m_groupid );
onContentChanged();
break;
default: default:
Assert.fail(); Assert.fail();
} }
} }
} }
@Override
public void onContentChanged()
{
super.onContentChanged();
if ( null != m_adapter ) {
m_adapter.expandGroups( getExpandableListView() );
}
}
@Override @Override
public void onCreateContextMenu( ContextMenu menu, View view, public void onCreateContextMenu( ContextMenu menu, View view,
ContextMenuInfo menuInfo ) ContextMenuInfo menuInfo )
{ {
MenuInflater inflater = getMenuInflater(); ExpandableListView.ExpandableListContextMenuInfo info
inflater.inflate( R.menu.games_list_item_menu, menu ); = (ExpandableListView.ExpandableListContextMenuInfo)menuInfo;
long packedPos = info.packedPosition;
int childPos = ExpandableListView.getPackedPositionChild( packedPos );
AdapterView.AdapterContextMenuInfo info = String name;
(AdapterView.AdapterContextMenuInfo)menuInfo; if ( 0 <= childPos ) { // game case
int position = info.position; MenuInflater inflater = getMenuInflater();
long rowid = DBUtils.gamesList( this )[position]; inflater.inflate( R.menu.games_list_item_menu, menu );
String title = GameUtils.getName( this, rowid );
long rowid = m_adapter.getRowIDFor( packedPos );
name = GameUtils.getName( this, rowid );
} else { // group case
MenuInflater inflater = getMenuInflater();
inflater.inflate( R.menu.games_list_group_menu, menu );
int pos = ExpandableListView.getPackedPositionGroup( packedPos );
name = m_adapter.groupNames()[pos];
if ( 0 == pos ) {
Utils.setItemEnabled( menu, R.id.list_group_moveup, false );
}
if ( pos + 1 == m_adapter.getGroupCount() ) {
Utils.setItemEnabled( menu, R.id.list_group_movedown, false );
}
if ( XWPrefs.getDefaultNewGameGroup( this )
== m_adapter.getGroupIDFor( pos ) ) {
Utils.setItemEnabled( menu, R.id.list_group_default, false );
Utils.setItemEnabled( menu, R.id.list_group_delete, false );
}
}
menu.setHeaderTitle( getString( R.string.game_item_menu_titlef, menu.setHeaderTitle( getString( R.string.game_item_menu_titlef,
title ) ); name ) );
} }
@Override @Override
public boolean onContextItemSelected( MenuItem item ) public boolean onContextItemSelected( MenuItem item )
{ {
AdapterView.AdapterContextMenuInfo info; ExpandableListContextMenuInfo info;
try { try {
info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); info = (ExpandableListContextMenuInfo)item.getMenuInfo();
} catch (ClassCastException cce) { } catch (ClassCastException cce) {
DbgUtils.loge( cce ); DbgUtils.loge( cce );
return false; return false;
} }
return handleMenuItem( item.getItemId(), info.position ); long packedPos = info.packedPosition;
int childPos = ExpandableListView.getPackedPositionChild( packedPos );
int groupPos = ExpandableListView.getPackedPositionGroup(packedPos);
int menuID = item.getItemId();
boolean handled;
if ( 0 <= childPos ) {
long rowid = m_adapter.getRowIDFor( groupPos, childPos );
handled = handleGameMenuItem( menuID, rowid );
} else {
handled = handleGroupMenuItem( menuID, groupPos );
}
return handled;
} // onContextItemSelected } // onContextItemSelected
@Override @Override
@ -520,7 +647,7 @@ public class GamesList extends XWListActivity
@Override @Override
public boolean onPrepareOptionsMenu( Menu menu ) public boolean onPrepareOptionsMenu( Menu menu )
{ {
boolean visible = XWPrefs.getDebugEnabled( this ) ; boolean visible = XWPrefs.getDebugEnabled( this );
for ( int id : DEBUGITEMS ) { for ( int id : DEBUGITEMS ) {
MenuItem item = menu.findItem( id ); MenuItem item = menu.findItem( id );
item.setVisible( visible ); item.setVisible( visible );
@ -544,12 +671,8 @@ public class GamesList extends XWListActivity
startNewGameActivity(); startNewGameActivity();
break; break;
case R.id.gamel_menu_delete_all: case R.id.gamel_menu_newgroup:
if ( DBUtils.gamesList( this ).length > 0 ) { showDialog( NEW_GROUP );
showConfirmThen( R.string.confirm_delete_all,
R.string.button_delete, DELETE_ALL_ACTION );
}
handled = true;
break; break;
case R.id.gamel_menu_dicts: case R.id.gamel_menu_dicts:
@ -615,12 +738,12 @@ public class GamesList extends XWListActivity
} ); } );
} }
private boolean handleMenuItem( int menuID, int position ) private boolean handleGameMenuItem( int menuID, long rowid )
{ {
boolean handled = true; boolean handled = true;
DialogInterface.OnClickListener lstnr; DialogInterface.OnClickListener lstnr;
m_rowid = DBUtils.gamesList( this )[position]; m_rowid = rowid;
if ( R.id.list_item_delete == menuID ) { if ( R.id.list_item_delete == menuID ) {
showConfirmThen( R.string.confirm_delete, R.string.button_delete, showConfirmThen( R.string.confirm_delete, R.string.button_delete,
@ -638,7 +761,13 @@ public class GamesList extends XWListActivity
case R.id.list_item_rename: case R.id.list_item_rename:
showDialog( RENAME_GAME ); showDialog( RENAME_GAME );
break; break;
case R.id.list_item_move:
if ( 1 >= m_adapter.getGroupCount() ) {
showOKOnlyDialog( R.string.no_move_onegroup );
} else {
showDialog( CHANGE_GROUP );
}
break;
case R.id.list_item_new_from: case R.id.list_item_new_from:
showNotAgainDlgThen( R.string.not_again_newfrom, showNotAgainDlgThen( R.string.not_again_newfrom,
R.string.key_notagain_newfrom, R.string.key_notagain_newfrom,
@ -674,7 +803,48 @@ public class GamesList extends XWListActivity
} }
return handled; return handled;
} // handleMenuItem } // handleGameMenuItem
private boolean handleGroupMenuItem( int menuID, int groupPos )
{
boolean handled = true;
m_groupid = m_adapter.getGroupIDFor( groupPos );
switch ( menuID ) {
case R.id.list_group_delete:
if ( m_groupid == XWPrefs.getDefaultNewGameGroup( this ) ) {
showOKOnlyDialog( R.string.cannot_delete_default_group );
} else {
String msg = getString( R.string.group_confirm_del );
int nGames = m_adapter.getChildrenCount( groupPos );
if ( 0 < nGames ) {
msg += getString( R.string.group_confirm_delf, nGames );
}
showConfirmThen( msg, DELETE_GROUP_ACTION );
}
break;
case R.id.list_group_rename:
showDialog( RENAME_GROUP );
break;
case R.id.list_group_default:
XWPrefs.setDefaultNewGameGroup( this, m_groupid );
break;
case R.id.list_group_moveup:
if ( m_adapter.moveGroup( m_groupid, -1 ) ) {
onContentChanged();
}
break;
case R.id.list_group_movedown:
if ( m_adapter.moveGroup( m_groupid, 1 ) ) {
onContentChanged();
}
break;
default:
handled = false;
}
return handled;
}
private boolean checkWarnNoDict( NetLaunchInfo nli ) private boolean checkWarnNoDict( NetLaunchInfo nli )
{ {
@ -856,6 +1026,23 @@ public class GamesList extends XWListActivity
} }
} }
private Dialog buildNamerDlg( String curname, int labelID, int titleID,
DialogInterface.OnClickListener lstnr,
int dlgID )
{
m_namer = (GameNamer)Utils.inflate( this, R.layout.rename_game );
m_namer.setName( curname );
m_namer.setLabel( labelID );
Dialog dialog = new AlertDialog.Builder( this )
.setTitle( titleID )
.setNegativeButton( R.string.button_cancel, null )
.setPositiveButton( R.string.button_ok, lstnr )
.setView( m_namer )
.create();
Utils.setRemoveOnDismiss( this, dialog, dlgID );
return dialog;
}
private boolean makeNewNetGameIf() private boolean makeNewNetGameIf()
{ {
boolean madeGame = null != m_netLaunchInfo; boolean madeGame = null != m_netLaunchInfo;

View file

@ -23,28 +23,22 @@ package org.eehouse.android.xw4;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.ContentResolver;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.method.DialerKeyListener; import android.text.method.DialerKeyListener;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ListView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Iterator; import java.util.Iterator;
import junit.framework.Assert;
public class SMSInviteActivity extends InviteActivity { public class SMSInviteActivity extends InviteActivity {
@ -251,31 +245,30 @@ public class SMSInviteActivity extends InviteActivity {
// long time to return. Be safe. // long time to return. Be safe.
if ( null != cursor && !cursor.isClosed() ) { if ( null != cursor && !cursor.isClosed() ) {
if ( cursor.moveToFirst() ) { if ( cursor.moveToFirst() ) {
String name = String name =
cursor.getString( cursor. cursor.getString( cursor.
getColumnIndex( Phone.DISPLAY_NAME)); getColumnIndex( Phone.DISPLAY_NAME));
String number = String number =
cursor.getString( cursor. cursor.getString( cursor.
getColumnIndex( Phone.NUMBER ) ); getColumnIndex( Phone.NUMBER ) );
int type = cursor.getInt( cursor. int type = cursor.getInt( cursor.
getColumnIndex( Phone.TYPE ) ); getColumnIndex( Phone.TYPE ) );
m_pendingName = name; m_pendingName = name;
m_pendingNumber = number; m_pendingNumber = number;
if ( Phone.TYPE_MOBILE == type ) { if ( Phone.TYPE_MOBILE == type ) {
showConfirmThen( R.string.warn_unlimited, showConfirmThen( R.string.warn_unlimited,
R.string.button_yes, R.string.button_yes,
POST_WARNING_ACTION ); POST_WARNING_ACTION );
} else { } else {
m_immobileConfirmed = false; m_immobileConfirmed = false;
String msg = String msg =
Utils.format( this, R.string.warn_nomobilef, Utils.format( this, R.string.warn_nomobilef,
number, name ); number, name );
showConfirmThen( msg, R.string.button_yes, showConfirmThen( msg, R.string.button_yes,
USE_IMMOBILE_ACTION ); USE_IMMOBILE_ACTION );
} }
} }
cursor.close();
} }
} // addPhoneNumbers } // addPhoneNumbers

View file

@ -353,6 +353,12 @@ public class Utils {
item.setVisible( enabled ); item.setVisible( enabled );
} }
public static void setItemEnabled( Menu menu, int id, boolean enabled )
{
MenuItem item = menu.findItem( id );
item.setEnabled( enabled );
}
public static boolean hasSmallScreen( Context context ) public static boolean hasSmallScreen( Context context )
{ {
if ( null == s_hasSmallScreen ) { if ( null == s_hasSmallScreen ) {

View file

@ -0,0 +1,129 @@
/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */
/*
* Copyright 2010 - 2012 by Eric House (xwords@eehouse.org). All
* rights reserved.
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.eehouse.android.xw4;
import android.app.Dialog;
import android.app.ExpandableListActivity;
import android.content.DialogInterface;
import android.os.Bundle;
import junit.framework.Assert;
public class XWExpandableListActivity extends ExpandableListActivity
implements DlgDelegate.DlgClickNotify, MultiService.MultiEventListener {
private DlgDelegate m_delegate;
@Override
protected void onCreate( Bundle savedInstanceState )
{
DbgUtils.logf( "%s.onCreate(this=%H)", getClass().getName(), this );
super.onCreate( savedInstanceState );
m_delegate = new DlgDelegate( this, this, savedInstanceState );
}
@Override
protected void onSaveInstanceState( Bundle outState )
{
super.onSaveInstanceState( outState );
m_delegate.onSaveInstanceState( outState );
}
@Override
protected Dialog onCreateDialog( final int id )
{
DbgUtils.logf( "%s.onCreateDialog() called", getClass().getName() );
Dialog dialog = m_delegate.onCreateDialog( id );
if ( null == dialog ) {
dialog = super.onCreateDialog( id );
}
return dialog;
}
@Override
protected void onPrepareDialog( int id, Dialog dialog )
{
super.onPrepareDialog( id, dialog );
m_delegate.onPrepareDialog( id, dialog );
}
protected boolean post( Runnable runnable )
{
return m_delegate.post( runnable );
}
protected void doSyncMenuitem()
{
m_delegate.doSyncMenuitem();
}
protected void showNotAgainDlgThen( int msgID, int prefsKey,
int action )
{
m_delegate.showNotAgainDlgThen( msgID, prefsKey, action );
}
protected void showNotAgainDlg( int msgID, int prefsKey )
{
m_delegate.showNotAgainDlgThen( msgID, prefsKey );
}
// It sucks that these must be duplicated here and XWActivity
protected void showAboutDialog()
{
m_delegate.showAboutDialog();
}
protected void showOKOnlyDialog( int msgID )
{
m_delegate.showOKOnlyDialog( msgID );
}
protected void showConfirmThen( String msg, int action )
{
m_delegate.showConfirmThen( msg, action );
}
protected void showConfirmThen( String msg, int posButton, int action )
{
m_delegate.showConfirmThen( msg, posButton, action );
}
protected void showConfirmThen( int msg, int posButton, int action )
{
m_delegate.showConfirmThen( getString(msg), posButton, action );
}
// DlgDelegate.DlgClickNotify interface
public void dlgButtonClicked( int id, int which )
{
Assert.fail();
}
// BTService.BTEventListener interface
public void eventOccurred( MultiService.MultiEvent event,
final Object ... args )
{
m_delegate.eventOccurred( event, args );
}
}

View file

@ -105,12 +105,6 @@ public class XWListActivity extends ListActivity
m_delegate.onPrepareDialog( id, dialog ); m_delegate.onPrepareDialog( id, dialog );
} }
// It sucks that these must be duplicated here and XWActivity
protected void showAboutDialog()
{
m_delegate.showAboutDialog();
}
protected void showNotAgainDlgThen( int msgID, int prefsKey, protected void showNotAgainDlgThen( int msgID, int prefsKey,
int action ) int action )
{ {

View file

@ -26,7 +26,6 @@ import android.preference.PreferenceManager;
import android.text.TextUtils; import android.text.TextUtils;
import com.google.android.gcm.GCMRegistrar; import com.google.android.gcm.GCMRegistrar;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.ArrayList;
public class XWPrefs { public class XWPrefs {
@ -305,6 +304,29 @@ public class XWPrefs {
setPrefsLong( context, R.string.key_default_group, val ); setPrefsLong( context, R.string.key_default_group, val );
} }
public static void setGroupPositions( Context context, long[] posns )
{
String[] asStrs = new String[posns.length];
for ( int ii = 0; ii < posns.length; ++ii ) {
asStrs[ii] = String.format( "%d", posns[ii] );
}
setPrefsStringArray( context, R.string.key_group_posns, asStrs );
}
public static long[] getGroupPositions( Context context )
{
long[] posns = null;
String[] longStrs = getPrefsStringArray( context,
R.string.key_group_posns );
if ( null != longStrs ) {
posns = new long[longStrs.length];
for ( int ii = 0; ii < longStrs.length; ++ii ) {
posns[ii] = Long.parseLong(longStrs[ii]);
}
}
return posns;
}
protected static String getPrefsString( Context context, int keyID ) protected static String getPrefsString( Context context, int keyID )
{ {
String key = context.getString( keyID ); String key = context.getString( keyID );

View file

@ -224,9 +224,9 @@ public class GameSummary {
&& serverRole != DeviceRole.SERVER_STANDALONE ); && serverRole != DeviceRole.SERVER_STANDALONE );
} }
private boolean isLocal( int indx ) { private boolean isLocal( int indx )
int flag = 2 << (indx * 2); {
return 0 == (m_giFlags & flag); return localTurnNextImpl( m_giFlags, indx );
} }
private boolean isRobot( int indx ) { private boolean isRobot( int indx ) {
@ -328,4 +328,19 @@ public class GameSummary {
return String.format( "%s%s%s", separator, list, separator ); return String.format( "%s%s%s", separator, list, separator );
} }
private static boolean localTurnNextImpl( int flags, int turn )
{
int flag = 2 << (turn * 2);
return 0 == (flags & flag);
}
public static Boolean localTurnNext( int flags, int turn )
{
Boolean result = null;
if ( 0 <= turn ) {
result = new Boolean( localTurnNextImpl( flags, turn ) );
}
return result;
}
} }

View file

@ -292,7 +292,7 @@ public class JNIThread extends Thread {
} }
byte[] state = XwJNI.game_saveToStream( m_jniGamePtr, m_gi ); byte[] state = XwJNI.game_saveToStream( m_jniGamePtr, m_gi );
if ( Arrays.equals( m_gameAtStart, state ) ) { if ( Arrays.equals( m_gameAtStart, state ) ) {
DbgUtils.logf( "no change in game; can skip saving" ); // DbgUtils.logf( "no change in game; can skip saving" );
} else { } else {
GameSummary summary = new GameSummary( m_context, m_gi ); GameSummary summary = new GameSummary( m_context, m_gi );
XwJNI.game_summarize( m_jniGamePtr, summary ); XwJNI.game_summarize( m_jniGamePtr, summary );

View file

@ -743,12 +743,14 @@ warnBadWords( const XP_UCHAR* word, XP_Bool isLegal,
{ {
XP_Bool ok = XP_TRUE; XP_Bool ok = XP_TRUE;
if ( !isLegal ) { if ( !isLegal ) {
BadWordInfo bwi; BadWordInfo bwi = {0};
BoardCtxt* board = (BoardCtxt*)closure; BoardCtxt* board = (BoardCtxt*)closure;
XP_S16 turn = server_getCurrentTurn( board->server ); XP_S16 turn = server_getCurrentTurn( board->server );
bwi.nWords = 1; bwi.nWords = 1;
bwi.words[0] = word; bwi.words[0] = word;
bwi.dictName =
dict_getShortName( model_getPlayerDict( board->model, turn ) );
ok = !board->badWordRejected ok = !board->badWordRejected
&& util_warnIllegalWord( board->util, &bwi, turn, XP_FALSE ); && util_warnIllegalWord( board->util, &bwi, turn, XP_FALSE );

View file

@ -82,7 +82,7 @@ typedef struct PickInfo {
typedef struct BadWordInfo { typedef struct BadWordInfo {
XP_U16 nWords; XP_U16 nWords;
XP_UCHAR* dictName; const XP_UCHAR* dictName;
const XP_UCHAR* words[MAX_TRAY_TILES+1]; /* can form in both directions */ const XP_UCHAR* words[MAX_TRAY_TILES+1]; /* can form in both directions */
} BadWordInfo; } BadWordInfo;

View file

@ -1708,8 +1708,8 @@ gtk_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 player,
result = XP_TRUE; result = XP_TRUE;
} else { } else {
XP_ASSERT( bwi->nWords == 1 ); XP_ASSERT( bwi->nWords == 1 );
sprintf( buf, "Word \"%s\" not in the current dictionary. " sprintf( buf, "Word \"%s\" not in the current dictionary (%s). "
"Use it anyway?", bwi->words[0] ); "Use it anyway?", bwi->words[0], bwi->dictName );
result = gtkask( globals->window, buf, GTK_BUTTONS_YES_NO ); result = gtkask( globals->window, buf, GTK_BUTTONS_YES_NO );
} }