add invite history and ability to invite all at once

If inviting known players to a more-than-two-player game, can select all
at once. Required using checkboxes instead of radiobuttons for the case
where nMissing > 1.
This commit is contained in:
Eric House 2020-11-21 15:11:59 -08:00
parent 210b0bcbcb
commit d5be06413f
12 changed files with 231 additions and 58 deletions

View file

@ -682,7 +682,8 @@ public class BoardDelegate extends DelegateBase
private void showInviteChoicesThen() private void showInviteChoicesThen()
{ {
NetLaunchInfo nli = nliForMe(); NetLaunchInfo nli = nliForMe();
showInviteChoicesThen( Action.LAUNCH_INVITE_ACTION, nli ); showInviteChoicesThen( Action.LAUNCH_INVITE_ACTION, nli,
m_mySIS.nMissing );
} }
@Override @Override
@ -1111,8 +1112,13 @@ public class BoardDelegate extends DelegateBase
break; break;
case LAUNCH_INVITE_ACTION: case LAUNCH_INVITE_ACTION:
CommsAddrRec addr = (CommsAddrRec)params[0]; for ( Object obj : params ) {
tryOtherInvites( addr ); if ( obj instanceof CommsAddrRec ) {
tryOtherInvites( (CommsAddrRec)obj );
} else {
break;
}
}
break; break;
case ENABLE_NBS_DO: case ENABLE_NBS_DO:
@ -1510,6 +1516,23 @@ public class BoardDelegate extends DelegateBase
callInviteChoices(); callInviteChoices();
} }
@Override
public void onInfoClicked()
{
SentInvitesInfo sentInfo = DBUtils.getInvitesFor( m_activity, m_rowid );
String msg = sentInfo.getAsText( m_activity );
makeOkOnlyBuilder( msg )
.setTitle( R.string.title_invite_history )
.setAction( Action.INVITE_INFO )
.show();
}
@Override
public long getRowID()
{
return m_rowid;
}
private byte[] getInvite() private byte[] getInvite()
{ {
byte[] result = null; byte[] result = null;

View file

@ -595,10 +595,10 @@ public abstract class DelegateBase implements DlgClickNotify,
m_dlgDelegate.launchLookup( words, lang, !studyOn ); m_dlgDelegate.launchLookup( words, lang, !studyOn );
} }
protected void showInviteChoicesThen( Action action, protected void showInviteChoicesThen( Action action, NetLaunchInfo nli,
NetLaunchInfo nli ) int nMissing )
{ {
m_dlgDelegate.showInviteChoicesThen( action, nli ); m_dlgDelegate.showInviteChoicesThen( action, nli, nMissing );
} }
public Builder makeOkOnlyBuilder( int msgID ) public Builder makeOkOnlyBuilder( int msgID )

View file

@ -401,11 +401,11 @@ public class DlgDelegate {
} }
public void showInviteChoicesThen( final Action action, public void showInviteChoicesThen( final Action action,
NetLaunchInfo nli ) NetLaunchInfo nli, int nMissing )
{ {
DlgState state = new DlgState( DlgID.INVITE_CHOICES_THEN ) DlgState state = new DlgState( DlgID.INVITE_CHOICES_THEN )
.setAction( action ) .setAction( action )
.setParams( nli ); .setParams( nli, nMissing );
m_dlgt.show( state ); m_dlgt.show( state );
} }

View file

@ -84,10 +84,14 @@ public class InviteChoicesAlert extends DlgDelegateAlert
InviteMeans lastMeans = null; InviteMeans lastMeans = null;
NetLaunchInfo nli = null; NetLaunchInfo nli = null;
Object[] params = state.getParams(); Object[] params = state.getParams();
int nMissing = 0;
if ( null != params ) { if ( null != params ) {
if ( 0 < params.length && params[0] instanceof NetLaunchInfo ) { if ( 0 < params.length && params[0] instanceof NetLaunchInfo ) {
nli = (NetLaunchInfo)params[0]; nli = (NetLaunchInfo)params[0];
} }
if ( 1 < params.length && params[1] instanceof Integer ) {
nMissing = (Integer)params[1];
}
} }
means.add( InviteMeans.EMAIL ); means.add( InviteMeans.EMAIL );
means.add( InviteMeans.SMS_USER ); means.add( InviteMeans.SMS_USER );
@ -130,11 +134,15 @@ public class InviteChoicesAlert extends DlgDelegateAlert
InviteMeans means = (InviteMeans)choice; InviteMeans means = (InviteMeans)choice;
activity.inviteChoiceMade( state.m_action, activity.inviteChoiceMade( state.m_action,
means, state.getParams() ); means, state.getParams() );
} else if ( choice instanceof String ) { } else if ( choice instanceof String[] ) {
String player = (String)choice; String[] players = (String[])choice;
Object[] params = new Object[players.length];
for ( int ii = 0; ii < params.length; ++ii ) {
String player = players[ii];
CommsAddrRec addr = XwJNI.kplr_getAddr( player ); CommsAddrRec addr = XwJNI.kplr_getAddr( player );
params[ii] = addr;
}
XWActivity xwact = (XWActivity)context; XWActivity xwact = (XWActivity)context;
Object[] params = { addr };
xwact.onPosButton( state.m_action, params ); xwact.onPosButton( state.m_action, params );
} else { } else {
Assert.failDbg(); Assert.failDbg();
@ -151,7 +159,7 @@ public class InviteChoicesAlert extends DlgDelegateAlert
; ;
String[] players = XwJNI.kplr_getPlayers(); String[] players = XwJNI.kplr_getPlayers();
mInviteView.setChoices( means, lastSelMeans, players ) mInviteView.setChoices( means, lastSelMeans, players, nMissing )
.setNli( nli ) .setNli( nli )
.setCallbacks( this ) .setCallbacks( this )
; ;

View file

@ -59,10 +59,9 @@ public class InviteView extends ScrollView
private ItemClicked mProcs; private ItemClicked mProcs;
private boolean mIsWho; private boolean mIsWho;
private RadioGroup mGroupTab; private RadioGroup mGroupTab;
private RadioGroup mGroupWho; private LimSelGroup mGroupWho;
private RadioGroup mGroupHow; private RadioGroup mGroupHow;
private Map<RadioButton, InviteMeans> mHowMeans = new HashMap<>(); private Map<RadioButton, InviteMeans> mHowMeans = new HashMap<>();
private Map<RadioButton, String> mWhoPlayers = new HashMap<>();
private boolean mExpanded = false; private boolean mExpanded = false;
private NetLaunchInfo mNli; private NetLaunchInfo mNli;
@ -71,7 +70,7 @@ public class InviteView extends ScrollView
} }
public InviteView setChoices( List<InviteMeans> meansList, int sel, public InviteView setChoices( List<InviteMeans> meansList, int sel,
String[] players ) String[] players, int maxPlayers )
{ {
final Context context = getContext(); final Context context = getContext();
@ -104,15 +103,10 @@ public class InviteView extends ScrollView
} }
if ( haveWho ) { if ( haveWho ) {
mGroupWho = (RadioGroup)findViewById( R.id.group_who ); mGroupWho = ((LimSelGroup)findViewById( R.id.group_who ))
mGroupWho.setOnCheckedChangeListener( this ); .setLimit( maxPlayers )
for ( String player : players ) { .addPlayers( players )
RadioButton button = (RadioButton)LocUtils ;
.inflate( context, R.layout.invite_radio );
button.setText( player );
mGroupWho.addView( button );
mWhoPlayers.put( button, player );
}
} }
mIsWho = false; // start with how mIsWho = false; // start with how
showWhoOrHow(); showWhoOrHow();
@ -139,20 +133,22 @@ public class InviteView extends ScrollView
return this; return this;
} }
public InviteView setCallbacks( ItemClicked procs ) { public InviteView setCallbacks( ItemClicked procs )
{
mProcs = procs; mProcs = procs;
mGroupWho.setCallbacks( procs );
return this; return this;
} }
public Object getChoice() public Object getChoice()
{ {
Object result = null; Object result = null;
RadioButton checked = getCurCheckedFor();
if ( null != checked ) {
if ( mIsWho ) { if ( mIsWho ) {
result = mWhoPlayers.get(checked); result = mGroupWho.getSelected();
} else { } else {
result = mHowMeans.get(checked); int curSel = mGroupHow.getCheckedRadioButtonId();
if ( 0 <= curSel ) {
result = (RadioButton)findViewById(curSel);
} }
} }
return result; return result;
@ -187,17 +183,6 @@ public class InviteView extends ScrollView
.setVisibility( show ? View.VISIBLE: View.GONE ); .setVisibility( show ? View.VISIBLE: View.GONE );
} }
private RadioButton getCurCheckedFor()
{
RadioButton result = null;
RadioGroup group = mIsWho ? mGroupWho : mGroupHow;
int curSel = group.getCheckedRadioButtonId();
if ( 0 <= curSel ) {
result = (RadioButton)findViewById(curSel);
}
return result;
}
private void showWhoOrHow() private void showWhoOrHow()
{ {
if ( null != mGroupWho ) { if ( null != mGroupWho ) {
@ -205,7 +190,7 @@ public class InviteView extends ScrollView
} }
mGroupHow.setVisibility( mIsWho ? View.INVISIBLE : View.VISIBLE ); mGroupHow.setVisibility( mIsWho ? View.INVISIBLE : View.VISIBLE );
boolean showEmpty = mIsWho && 0 == mWhoPlayers.size(); boolean showEmpty = mIsWho && 0 == mGroupWho.getChildCount();
findViewById( R.id.who_empty ) findViewById( R.id.who_empty )
.setVisibility( showEmpty ? View.VISIBLE : View.INVISIBLE ); .setVisibility( showEmpty ? View.VISIBLE : View.INVISIBLE );
} }

View file

@ -28,6 +28,7 @@ import android.content.DialogInterface;
import java.io.Serializable; import java.io.Serializable;
import org.eehouse.android.xw4.DBUtils.SentInvitesInfo;
import org.eehouse.android.xw4.DlgDelegate.Action; import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.Perms23.Perm; import org.eehouse.android.xw4.Perms23.Perm;
import org.eehouse.android.xw4.loc.LocUtils; import org.eehouse.android.xw4.loc.LocUtils;
@ -57,12 +58,14 @@ class InvitesNeededAlert {
interface Callbacks { interface Callbacks {
DelegateBase getDelegate(); DelegateBase getDelegate();
long getRowID();
void onCloseClicked(); void onCloseClicked();
void onInviteClicked(); void onInviteClicked();
void onInfoClicked();
} }
static void showOrHide( Callbacks callbacks, int nDevsSeen, int nPlayersMissing, static void showOrHide( Callbacks callbacks, int nDevsSeen,
boolean isRematch ) int nPlayersMissing, boolean isRematch )
{ {
DbgUtils.assertOnUIThread(); DbgUtils.assertOnUIThread();
InvitesNeededAlert self = sInstance[0]; InvitesNeededAlert self = sInstance[0];
@ -102,8 +105,8 @@ class InvitesNeededAlert {
} }
} }
private static void makeNew( Callbacks callbacks, private static void makeNew( Callbacks callbacks, int nDevsSeen,
int nDevsSeen, int nPlayersMissing, boolean isRematch ) int nPlayersMissing, boolean isRematch )
{ {
Log.d( TAG, "makeNew(nDevsSeen=%d, nPlayersMissing=%d)", nDevsSeen, nPlayersMissing ); Log.d( TAG, "makeNew(nDevsSeen=%d, nPlayersMissing=%d)", nDevsSeen, nPlayersMissing );
State state = new State( nDevsSeen, nPlayersMissing, isRematch ); State state = new State( nDevsSeen, nPlayersMissing, isRematch );
@ -174,6 +177,22 @@ class InvitesNeededAlert {
} }
} ); } );
if ( BuildConfig.NON_RELEASE ) {
long rowid = mCallbacks.getRowID();
SentInvitesInfo sentInfo = DBUtils.getInvitesFor( context, rowid );
int nSent = sentInfo.getMinPlayerCount();
boolean invitesSent = nSent >= state.nPlayersMissing;
if ( invitesSent ) {
alert.setNoDismissListenerNeut( ab, R.string.newgame_invite_more,
new OnClickListener() {
@Override
public void onClick( DialogInterface dlg, int item ) {
onNeutClick();
}
} );
}
}
alert.setNoDismissListenerNeg( ab, R.string.button_close, alert.setNoDismissListenerNeg( ab, R.string.button_close,
new OnClickListener() { new OnClickListener() {
@Override @Override
@ -191,6 +210,11 @@ class InvitesNeededAlert {
mCallbacks.onInviteClicked(); mCallbacks.onInviteClicked();
} }
private void onNeutClick()
{
mCallbacks.onInfoClicked();
}
private void onNegClick() private void onNegClick()
{ {
Log.d( TAG, "onNegClick()" ); Log.d( TAG, "onNegClick()" );

View file

@ -0,0 +1,120 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2020 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.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eehouse.android.xw4.loc.LocUtils;
public class LimSelGroup extends LinearLayout
implements OnCheckedChangeListener {
private static final String TAG = LimSelGroup.class.getSimpleName();
private int mLimit;
private InviteView.ItemClicked mProcs;
public LimSelGroup( Context context, AttributeSet as )
{
super( context, as );
}
LimSelGroup setLimit( int limit )
{
Assert.assertTrueNR( 0 < limit );
mLimit = limit;
return this;
}
void setCallbacks( InviteView.ItemClicked procs )
{
mProcs = procs;
}
String[] getSelected()
{
String[] result = null;
int len = mChecked.size();
if ( 0 < len ) {
result = new String[mChecked.size()];
for ( int ii = 0; ii < result.length; ++ii ) {
result[ii] = mChecked.get(ii).getText().toString();
}
}
return result;
}
LimSelGroup addPlayers( String[] names )
{
Context context = getContext();
for ( String name : names ) {
CompoundButton button;
if ( 1 == mLimit ) {
button = (RadioButton)LocUtils.inflate( context, R.layout.invite_radio );
} else {
button = (CheckBox)LocUtils.inflate( context, R.layout.invite_checkbox );
}
button.setText( name );
button.setOnCheckedChangeListener( this );
addView( button );
}
return this;
}
@Override
public void onCheckedChanged( CompoundButton buttonView, boolean isChecked )
{
Log.d( TAG, "onCheckedChanged(%s, %b)", buttonView, isChecked );
addToSet( buttonView, isChecked );
if ( null != mProcs ) {
mProcs.checkButton();
}
}
ArrayList<CompoundButton> mChecked = new ArrayList<>();
private void addToSet( CompoundButton button, boolean nowChecked )
{
for ( Iterator<CompoundButton> iter = mChecked.iterator();
iter.hasNext(); ) {
CompoundButton but = iter.next();
if ( nowChecked ) {
Assert.assertTrueNR( ! but.equals(button) );
} else if ( but.equals(button) ) {
iter.remove();
}
}
if ( nowChecked ) {
mChecked.add( button );
while ( mLimit < mChecked.size() ) {
CompoundButton oldButton = mChecked.remove( 0 );
oldButton.setChecked( false );
}
}
}
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/invite_who_elem"
/>

View file

@ -1,9 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RadioButton xmlns:android="http://schemas.android.com/apk/res/android" <RadioButton xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" style="@style/invite_who_elem"
android:layout_height="match_parent"
android:textSize="22dp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
/> />

View file

@ -44,9 +44,11 @@
<FrameLayout android:layout_width="match_parent" <FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
> >
<RadioGroup android:id="@+id/group_who" <org.eehouse.android.xw4.LimSelGroup
android:id="@+id/group_who"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"
/> />
<RadioGroup android:id="@+id/group_how" <RadioGroup android:id="@+id/group_how"

View file

@ -1025,7 +1025,8 @@
<string name="newgame_invite">Invite now</string> <string name="newgame_invite">Invite now</string>
<!-- Button offering to invite Known Player to a new game --> <!-- Button offering to invite Known Player to a new game -->
<string name="invite_player_fmt">Invite %1$s</string> <string name="invite_player_fmt">Invite %1$s</string>
<string name="newgame_invite_more">More info</string> <string name="newgame_invite_more">History</string>
<string name="title_invite_history">Invitations Sent</string>
<string name="newgame_drop_relay">Drop Relay</string> <string name="newgame_drop_relay">Drop Relay</string>
<!-- EXPLAIN ME --> <!-- EXPLAIN ME -->

View file

@ -166,4 +166,12 @@
<item name="android:layout_weight">1</item> <item name="android:layout_weight">1</item>
<item name="android:drawSelectorOnTop">true</item> <item name="android:drawSelectorOnTop">true</item>
</style> </style>
<style name="invite_who_elem">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
<item name="android:textSize">22dp</item>
<item name="android:paddingTop">5dp</item>
<item name="android:paddingBottom">5dp</item>
</style>
</resources> </resources>