diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java index 4c02e9b40..f15ed3bb6 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java @@ -81,7 +81,7 @@ public class BoardDelegate extends DelegateBase implements TransportProcs.TPMsgHandler, View.OnClickListener, DwnldDelegate.DownloadFinishedListener, ConnStatusHandler.ConnStatusCBacks, - Wrapper.Procs { + Wrapper.Procs, InvitesNeededAlert.Callbacks { private static final String TAG = BoardDelegate.class.getSimpleName(); private static final int SCREEN_ON_TIME = 10 * 60 * 1000; // 10 mins @@ -124,11 +124,9 @@ public class BoardDelegate extends DelegateBase private boolean m_startSkipped; private JNIThread.GameStateInfo m_gsi; - private int m_nGuestDevs = -1; private boolean m_showedReInvite; private boolean m_overNotShown; private boolean m_dropRelayOnDismiss; - private DBAlert m_inviteAlert; private boolean m_haveStartedShowing; private Wrapper mNFCWrapper; @@ -163,12 +161,11 @@ public class BoardDelegate extends DelegateBase private static enum StartAlertOrder { NBS_PERMS, NO_MEANS, INVITE, DONE, }; private static class MySIS implements Serializable { - MySIS() { nMissing = -1; } String toastStr; String[] words; String getDict; - int nMissing; - int nAlerts; + int nMissing = -1; + int nGuestDevs; boolean inTrade; StartAlertOrder mAlertOrder = StartAlertOrder.values()[0]; } @@ -177,7 +174,8 @@ public class BoardDelegate extends DelegateBase private boolean alertOrderAt( StartAlertOrder ord ) { boolean result = m_mySIS.mAlertOrder == ord; - // Log.d( TAG, "alertOrderAt(%s) => %b", ord, result ); + // Log.d( TAG, "alertOrderAt(%s) => %b (at %s)", ord, result, + // m_mySIS.mAlertOrder ); return result; } @@ -414,10 +412,8 @@ public class BoardDelegate extends DelegateBase .setNegativeButton( R.string.button_no, null ) .create(); break; - case DLG_INVITE: { - InviteAlertState state = (InviteAlertState)params[0]; - dialog = makeInviteDialog( alert, state ); - } + case DLG_INVITE: + dialog = InvitesNeededAlert.make( this, alert, params ); break; case ENABLE_NFC: @@ -447,160 +443,6 @@ public class BoardDelegate extends DelegateBase } } - private static class InviteAlertState implements Serializable { - GameSummary summary; - CurGameInfo gi; - boolean relayMissing; - CommsConnTypeSet connTypes; - long rowid; - int nMissing; - } - - private Dialog makeInviteDialog( final DBAlert alert, - final InviteAlertState state ) - { - Dialog dialog = null; - final Activity activity = alert.getActivity(); - if ( null != activity ) { - final SentInvitesInfo[] sentInfo = { null }; - String message; - int titleID; - boolean showInviteButton = true; - boolean showNeutButton = false; - - int buttonTxt = R.string.newgame_invite; - - if ( state.relayMissing ) { - titleID = R.string.seeking_relay; - // If relay is only means, don't allow at all - boolean relayOnly = 1 >= state.connTypes.size(); - showInviteButton = !relayOnly; - message = activity.getString( R.string.no_relay_conn ); - if ( NetStateCache.netAvail( activity ) - && NetStateCache.onWifi() ) { - message += LocUtils.getString( activity, R.string.wifi_warning ); - } - if ( !relayOnly ) { - CommsConnTypeSet without = (CommsConnTypeSet) - state.connTypes.clone(); - without.remove( CommsConnType.COMMS_CONN_RELAY ); - message += "\n\n" - + LocUtils.getString( activity, - R.string.drop_relay_warning_fmt, - without.toString( activity, true ) ); - buttonTxt = R.string.newgame_drop_relay; - } - } else { - sentInfo[0] = DBUtils.getInvitesFor( activity, state.rowid ); - int nSent = sentInfo[0].getMinPlayerCount(); - boolean invitesSent = nSent >= state.nMissing; - if ( invitesSent ) { - m_showedReInvite = true; - if ( state.summary.hasRematchInfo() ) { - titleID = R.string.waiting_rematch_title; - message = LocUtils.getString( activity, R.string.rematch_msg ); - } else { - titleID = R.string.waiting_invite_title; - message = LocUtils - .getQuantityString( activity, R.plurals.invite_sent_fmt, - nSent, nSent, state.nMissing ); - } - buttonTxt = R.string.button_reinvite; - showNeutButton = true; - } else if ( DeviceRole.SERVER_ISCLIENT == state.gi.serverRole ) { - Assert.assertFalse( state.summary.hasRematchInfo() ); - message = LocUtils.getString( activity, R.string.invited_msg ); - titleID = R.string.waiting_title; - showInviteButton = false; - } else { - titleID = R.string.waiting_title; - message = LocUtils - .getQuantityString( activity, R.plurals.invite_msg_fmt, - state.nMissing, state.nMissing ); - } - - if ( ! invitesSent && showInviteButton ) { - String ps = null; - if ( state.nMissing > 1 ) { - ps = LocUtils.getString( activity, R.string.invite_multiple ); - } else { - boolean[] avail = NFCUtils.nfcAvail( activity ); - if ( avail[1] ) { - ps = LocUtils.getString( activity, R.string.invite_if_nfc ); - } - } - if ( null != ps ) { - message += "\n\n" + ps; - } - } - - message += "\n\n" + LocUtils.getString( activity, R.string.invite_stays ); - } - - AlertDialog.Builder ab = makeAlertBuilder() - .setTitle( titleID ) - .setMessage( message ); - - OnClickListener lstnr = new OnClickListener() { - @Override - public void onClick( DialogInterface dialog, int item ){ - if ( !state.relayMissing || - ! state.connTypes.contains(CommsConnType.COMMS_CONN_RELAY) ) { - Assert.assertTrue( 0 < state.nMissing ); - if ( state.summary.hasRematchInfo() ) { - tryRematchInvites( true ); - } else if ( state.summary.hasInviteInfo() ) { - tryOtherInvites(); - } else { - callInviteChoices( sentInfo[0] ); - } - } else { - askDropRelay(); - } - } - }; - alert.setNoDismissListenerPos( ab, buttonTxt, lstnr ); - - if ( showNeutButton ) { - lstnr = new OnClickListener() { - @Override - public void onClick( DialogInterface dialog, - int item ) { - String msg = sentInfo[0].getAsText( activity ); - makeOkOnlyBuilder( msg ) - .setAction( Action.INVITE_INFO ) - .show(); - } - }; - alert.setNoDismissListenerNeut( ab, R.string.newgame_invite_more, - lstnr ); - } - if ( showInviteButton ) { - lstnr = new OnClickListener() { - @Override - public void onClick( DialogInterface di, int item ) { - alert.dismiss(); - finish(); - } - }; - alert.setNoDismissListenerNeg( ab, R.string.button_close, lstnr ); - } - - dialog = ab.create(); - - // Hack: I can't prevent screen rotation from duplicating this alert - dismissInviteAlert(); - m_inviteAlert = alert; - alert.setOnCancelListener( new DBAlert.OnCancelListener() { - @Override - public void onCancelled( XWDialogFragment frag ) { - finish(); - } - } ); - } - return dialog; - } // makeInviteDialog - public BoardDelegate( Delegator delegator, Bundle savedInstanceState ) { super( delegator, savedInstanceState, R.layout.board, R.menu.board_menu ); @@ -829,19 +671,18 @@ public class BoardDelegate extends DelegateBase // loop of showing the rationale over and over. Android will always tell // us to show the rationale, but if we've done it already we need to go // straight to asking for the permission. - private void callInviteChoices( final SentInvitesInfo info ) + private void callInviteChoices() { Perms23.tryGetPermsNA( this, Perm.READ_PHONE_STATE, R.string.phone_state_rationale, R.string.key_na_perms_phonestate, - Action.ASKED_PHONE_STATE, info ); + Action.ASKED_PHONE_STATE ); } - private void showInviteChoicesThen( Object[] params ) + private void showInviteChoicesThen() { - SentInvitesInfo info = (SentInvitesInfo)params[0]; NetLaunchInfo nli = nliForMe(); - showInviteChoicesThen( Action.LAUNCH_INVITE_ACTION, info, nli ); + showInviteChoicesThen( Action.LAUNCH_INVITE_ACTION, nli ); } @Override @@ -1221,7 +1062,7 @@ public class BoardDelegate extends DelegateBase break; case ASKED_PHONE_STATE: - showInviteChoicesThen( params ); + showInviteChoicesThen(); break; case BLANK_PICKED: @@ -1309,7 +1150,7 @@ public class BoardDelegate extends DelegateBase finish(); break; case ASKED_PHONE_STATE: - showInviteChoicesThen( params ); + showInviteChoicesThen(); break; case INVITE_SMS_DATA: Perms23.Perm[] perms = (Perms23.Perm[])params[2]; @@ -1411,8 +1252,9 @@ public class BoardDelegate extends DelegateBase case SMS_USER: case EMAIL: case CLIPBOARD: - NetLaunchInfo nli = new NetLaunchInfo( m_activity, m_summary, m_gi, 1, - 1 + m_nGuestDevs ); + NetLaunchInfo nli = new NetLaunchInfo( m_activity, m_summary, m_gi, + 1, // nPlayers + 1 + m_mySIS.nGuestDevs ); // fc if ( m_relayMissing ) { nli.removeAddress( CommsConnType.COMMS_CONN_RELAY ); } @@ -1526,17 +1368,6 @@ public class BoardDelegate extends DelegateBase ////////////////////////////////////////////////// // TransportProcs.TPMsgHandler interface ////////////////////////////////////////////////// - @Override - public void tpmRelayConnd( final String room, final int devOrder, - final boolean allHere, final int nMissing ) - { - runOnUiThread( new Runnable() { - @Override - public void run() { - handleConndMessage( room, devOrder, allHere, nMissing ); // from here too - } - } ); - } @Override public void tpmRelayErrorProc( TransportProcs.XWRELAY_ERROR relayErr ) @@ -1655,14 +1486,38 @@ public class BoardDelegate extends DelegateBase // Do we need this? } + //////////////////////////////////////////////////////////// + // InvitesNeededAlert.Callbacks + //////////////////////////////////////////////////////////// + @Override + public DelegateBase getDelegate() { return this; } + + @Override + public void onCloseClicked() + { + post( new Runnable() { + @Override + public void run() { + InvitesNeededAlert.dismiss(); + finish(); + } + } ); + } + + @Override + public void onInviteClicked() + { + callInviteChoices(); + } + private byte[] getInvite() { byte[] result = null; if ( 0 < m_mySIS.nMissing // Isn't there a better test?? && DeviceRole.SERVER_ISSERVER == m_gi.serverRole ) { NetLaunchInfo nli = new NetLaunchInfo( m_gi ); - Assert.assertTrue( 0 <= m_nGuestDevs ); - nli.forceChannel = 1 + m_nGuestDevs; + Assert.assertTrue( 0 <= m_mySIS.nGuestDevs ); + nli.forceChannel = 1 + m_mySIS.nGuestDevs; for ( Iterator iter = m_connTypes.iterator(); iter.hasNext(); ) { @@ -1774,68 +1629,6 @@ public class BoardDelegate extends DelegateBase return xpKey; } - private void handleConndMessage( String room, int devOrder, // <- hostID - boolean allHere, int nMissing ) - { - boolean skipDismiss = false; - - int naMsg = 0; - int naKey = 0; - String toastStr = null; - m_mySIS.nMissing = nMissing; - if ( allHere ) { - // All players have now joined the game. The device that - // created the room will assign tiles. Then it will be - // the first player's turn - - // Skip this until there's a way to show it only once per game - if ( false ) { - toastStr = getString( R.string.msg_relay_all_here_fmt, room ); - if ( devOrder > 1 ) { - naMsg = R.string.not_again_conndall; - naKey = R.string.key_notagain_conndall; - } - } - } else if ( nMissing > 0 ) { - if ( m_summary.hasRematchInfo() ) { - skipDismiss = !tryRematchInvites( false ); - } else if ( m_summary.hasInviteInfo() ) { - skipDismiss = !tryOtherInvites(); - } else if ( showInviteAlertIf() ) { - skipDismiss = true; - invalidateOptionsMenuIf(); - } else { - toastStr = getQuantityString( R.plurals.msg_relay_waiting_fmt, nMissing, - devOrder, room, nMissing ); - if ( devOrder == 1 ) { - naMsg = R.string.not_again_conndfirst; - naKey = R.string.key_notagain_conndfirst; - } else { - naMsg = R.string.not_again_conndmid; - naKey = R.string.key_notagain_conndmid; - } - } - } - - if ( null != toastStr ) { - Log.i( TAG, "handleConndMessage(): toastStr: %s", toastStr ); - m_mySIS.toastStr = toastStr; - if ( naMsg == 0 ) { - onPosButton( Action.SHOW_EXPL_ACTION, null ); - } else { - makeNotAgainBuilder( naMsg, naKey, Action.SHOW_EXPL_ACTION ) - .show(); - } - } - - if ( !skipDismiss ) { - dismissInviteAlert( nMissing, true ); // NO!!! - } - - invalidateOptionsMenuIf(); - setTitle(); - } // handleConndMessage - private class BoardUtilCtxt extends UtilCtxtImpl { public BoardUtilCtxt() @@ -2162,34 +1955,20 @@ public class BoardDelegate extends DelegateBase // missing or not. @Override public void informMissing( boolean isServer, CommsConnTypeSet connTypes, - int nDevs, final int nMissing ) + int nDevs, int nMissing ) { - boolean doDismiss = true; - m_connTypes = connTypes; - Assert.assertTrue( isServer || 0 == nMissing ); - // DbgUtils.logf( "BoardDelegate.informMissing(isServer=%b, nDevs=%d, nMissing=%d)", - // isServer, nDevs, nMissing ); - m_nGuestDevs = nDevs; - m_mySIS.nMissing = nMissing; // will be 0 unless isServer is true - setTitle(); + m_mySIS.nGuestDevs = nDevs; + m_connTypes = connTypes; - if ( null != connTypes && 0 == connTypes.size() ) { - askNoAddrsDelete(); - } else if ( 0 < nMissing && isServer ) { - doDismiss = false; - post( new Runnable() { + if ( isServer ) { + runOnUiThread( new Runnable() { @Override public void run() { showInviteAlertIf(); } } ); } - - // If we might have put up an alert earlier, take it down - if ( doDismiss ) { - dismissInviteAlert( nMissing, !m_relayMissing ); - } } @Override @@ -2570,42 +2349,6 @@ public class BoardDelegate extends DelegateBase } } - private void dismissInviteAlert( final int nMissing, final boolean connected ) - { - runOnUiThread( new Runnable() { - @Override - public void run() { - InviteChoicesAlert.dismissAny(); - - if ( m_relayMissing && connected ) { - m_relayMissing = false; - } - // Why this m_relayMissing thing? - if ( 0 == nMissing /* || !m_relayMissing*/ ) { - // Log.d( TAG, "dismissing invite alert %H", m_inviteAlert ); - dismissInviteAlert(); - } - } - } ); - } - - private void dismissInviteAlert() - { - if ( null != m_inviteAlert ) { - // Play Console reports a crash inside DialogFragment.dismiss() - // resulting from getFragmentManager() returning null. That - // probably means I'm on the way out and can safely drop? Let's - // see.... - try { - m_inviteAlert.dismiss(); - } catch ( NullPointerException | IllegalStateException ex ) { - Log.ex( TAG, ex ); - } - m_inviteAlert = null; - m_haveStartedShowing = false; - } - } - private void pingBTRemotes() { if ( null != m_connTypes @@ -2687,28 +2430,13 @@ public class BoardDelegate extends DelegateBase // This is failing sometimes, and so the null == m_inviteAlert test means // we never post it. BUT on a lot of devices without the test we wind up // trying over and over to put the thing up. - private boolean showInviteAlertIf() + private void showInviteAlertIf() { - boolean success = false; - DbgUtils.assertOnUIThread(); - if ( alertOrderAt( StartAlertOrder.INVITE ) ) { - if ( ! m_haveStartedShowing && null == m_inviteAlert - && m_mySIS.nMissing > 0 && !isFinishing() ) { - InviteAlertState ias = new InviteAlertState(); - ias.summary = m_summary; - ias.gi = m_gi; - ias.relayMissing = m_relayMissing; - ias.connTypes = m_connTypes; - ias.rowid = m_rowid; - ias.nMissing = m_mySIS.nMissing; - showDialogFragment( DlgID.DLG_INVITE, ias ); - m_haveStartedShowing = true; - success = true; - } else { - alertOrderIncrIfAt( StartAlertOrder.INVITE ); - } + if ( alertOrderAt( StartAlertOrder.INVITE ) && ! isFinishing() ) { + boolean isRematch = null != m_summary && m_summary.hasRematchInfo(); + InvitesNeededAlert.showOrHide( this, m_mySIS.nGuestDevs, + m_mySIS.nMissing, isRematch ); } - return success; } private boolean doZoom( int zoomBy ) @@ -2814,8 +2542,8 @@ public class BoardDelegate extends DelegateBase for ( int ii = 0; ii < m_missingDevs.length; ++ii ) { String dev = m_missingDevs[ii]; int nPlayers = m_missingCounts[ii]; - Assert.assertTrue( 0 <= m_nGuestDevs ); - int forceChannel = ii + m_nGuestDevs + 1; + Assert.assertTrue( 0 <= m_mySIS.nGuestDevs ); + int forceChannel = ii + m_mySIS.nGuestDevs + 1; NetLaunchInfo nli = new NetLaunchInfo( m_activity, m_summary, m_gi, nPlayers, forceChannel ) .setRemotesAreRobots( m_remotesAreRobots ); @@ -3177,7 +2905,7 @@ public class BoardDelegate extends DelegateBase private NetLaunchInfo nliForMe() { int numHere = 1; - int forceChannel = 1; + int forceChannel = 1 + m_mySIS.nGuestDevs; NetLaunchInfo nli = new NetLaunchInfo( m_activity, m_summary, m_gi, numHere, forceChannel ); return nli; @@ -3314,7 +3042,6 @@ public class BoardDelegate extends DelegateBase DBUtils.recordInviteSent( m_activity, m_rowid, means, dev ); if ( !invitesSent ) { - dismissInviteAlert(); Log.d( TAG, "recordInviteSent(): redoing invite alert" ); showInviteAlertIf(); } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/CommsTransport.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/CommsTransport.java index 055b4d067..a9caab875 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/CommsTransport.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/CommsTransport.java @@ -395,7 +395,7 @@ public class CommsTransport implements TransportProcs, public void relayConnd( String room, int devOrder, boolean allHere, int nMissing ) { - m_tpHandler.tpmRelayConnd( room, devOrder, allHere, nMissing ); + // m_tpHandler.tpmRelayConnd( room, devOrder, allHere, nMissing ); } @Override diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBAlert.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBAlert.java index f7f4951b0..38e210050 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBAlert.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBAlert.java @@ -99,7 +99,7 @@ public class DBAlert extends XWDialogFragment { if ( null == dialog ) { Log.e( TAG, "no dialog for %s from %s", getDlgID(), activity ); - Assert.failDbg(); + // Assert.failDbg(); // remove: better to see what users will see dialog = LocUtils.makeAlertBuilder( activity ) .setMessage( "Unable to create " + getDlgID() + " Alert" ) .setPositiveButton( "Bummer", null ) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DelegateBase.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DelegateBase.java index e05c614d1..0fbfda295 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DelegateBase.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DelegateBase.java @@ -519,7 +519,7 @@ public abstract class DelegateBase implements DlgClickNotify, protected void show( XWDialogFragment df ) { DbgUtils.assertOnUIThread(); - if ( m_activity instanceof XWActivity ) { + if ( null != df && m_activity instanceof XWActivity ) { ((XWActivity)m_activity).show( df ); } else { Assert.failDbg(); @@ -596,10 +596,9 @@ public abstract class DelegateBase implements DlgClickNotify, } protected void showInviteChoicesThen( Action action, - DBUtils.SentInvitesInfo info, - NetLaunchInfo nli) + NetLaunchInfo nli ) { - m_dlgDelegate.showInviteChoicesThen( action, info, nli ); + m_dlgDelegate.showInviteChoicesThen( action, nli ); } public Builder makeOkOnlyBuilder( int msgID ) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java index a0d741c71..948a7db67 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java @@ -401,12 +401,11 @@ public class DlgDelegate { } public void showInviteChoicesThen( final Action action, - SentInvitesInfo info, NetLaunchInfo nli ) { DlgState state = new DlgState( DlgID.INVITE_CHOICES_THEN ) .setAction( action ) - .setParams( info, nli ); + .setParams( nli ); m_dlgt.show( state ); } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteChoicesAlert.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteChoicesAlert.java index ccf77cc56..aa3ad85d5 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteChoicesAlert.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteChoicesAlert.java @@ -85,11 +85,8 @@ public class InviteChoicesAlert extends DlgDelegateAlert NetLaunchInfo nli = null; Object[] params = state.getParams(); if ( null != params ) { - if ( 0 < params.length && params[0] instanceof SentInvitesInfo ) { - lastMeans = ((SentInvitesInfo)params[0]).getLastMeans(); - } - if ( 1 < params.length && params[1] instanceof NetLaunchInfo ) { - nli = (NetLaunchInfo)params[1]; + if ( 0 < params.length && params[0] instanceof NetLaunchInfo ) { + nli = (NetLaunchInfo)params[0]; } } means.add( InviteMeans.EMAIL ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteView.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteView.java index 78a9799c8..a627e7d2c 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteView.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteView.java @@ -28,6 +28,7 @@ import android.widget.ImageView; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.ScrollView; +import android.widget.TextView; import java.util.HashMap; import java.util.List; @@ -238,6 +239,11 @@ public class InviteView extends ScrollView public void run() { ImageView iv = (ImageView)findViewById( R.id.qr_view ); iv.setImageBitmap( bitmap ); + if ( BuildConfig.NON_RELEASE ) { + TextView tv = (TextView)findViewById( R.id.qr_url ); + tv.setVisibility( View.VISIBLE ); + tv.setText( url ); + } post ( new Runnable() { @Override public void run() { diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InvitesNeededAlert.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InvitesNeededAlert.java new file mode 100644 index 000000000..fb6580a25 --- /dev/null +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InvitesNeededAlert.java @@ -0,0 +1,199 @@ +/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */ +/* + * Copyright 2009 - 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.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface; + +import java.io.Serializable; + +import org.eehouse.android.xw4.DlgDelegate.Action; +import org.eehouse.android.xw4.Perms23.Perm; +import org.eehouse.android.xw4.loc.LocUtils; + +class InvitesNeededAlert { + private static final String TAG = InvitesNeededAlert.class.getSimpleName(); + + private static InvitesNeededAlert[] sInstance = {null}; + + private DBAlert mAlert; + private State mState; + private Callbacks mCallbacks; + private DelegateBase mDelegate; + + private static class State implements Serializable { + int nDevsSeen; + int nPlayersMissing; + boolean isRematch; + + State( int nDevs, int nPlayers, boolean rematch ) + { + nDevsSeen = nDevs; + nPlayersMissing = nPlayers; + isRematch = rematch; + } + } + + interface Callbacks { + DelegateBase getDelegate(); + void onCloseClicked(); + void onInviteClicked(); + } + + static void showOrHide( Callbacks callbacks, int nDevsSeen, int nPlayersMissing, + boolean isRematch ) + { + DbgUtils.assertOnUIThread(); + InvitesNeededAlert self = sInstance[0]; + Log.d( TAG, "showOnceIf(nDevsSeen=%d, nPlayersMissing=%d); self: %s", + nDevsSeen, nPlayersMissing, self ); + + if ( null == self && 0 == nPlayersMissing ) { + // cool: need and have nothing, so do nothing + } else if ( 0 < nPlayersMissing && null == self ) { // Need but don't have + makeNew( callbacks, nDevsSeen, nPlayersMissing, isRematch ); + } else if ( 0 == nPlayersMissing && null != self ) { // Have and need to close + close( self ); + } else if ( null != self && nPlayersMissing != self.mState.nPlayersMissing ) { + close( self ); + makeNew( callbacks, nDevsSeen, nPlayersMissing, isRematch ); + } else if ( null != self && nPlayersMissing == self.mState.nPlayersMissing ) { + // nothing to do + } else { + Assert.failDbg(); + } + } + + static Dialog make( Callbacks callbacks, DBAlert alert, Object[] params ) + { + DbgUtils.assertOnUIThread(); + InvitesNeededAlert self = sInstance[0]; + return self.makeImpl( callbacks, alert, params ); + } + + static void dismiss() + { + Log.d( TAG, "dismiss()" ); + DbgUtils.assertOnUIThread(); + InvitesNeededAlert self = sInstance[0]; + if ( null != self ) { + close( self ); + } + } + + private static void makeNew( Callbacks callbacks, + int nDevsSeen, int nPlayersMissing, boolean isRematch ) + { + Log.d( TAG, "makeNew(nDevsSeen=%d, nPlayersMissing=%d)", nDevsSeen, nPlayersMissing ); + State state = new State( nDevsSeen, nPlayersMissing, isRematch ); + InvitesNeededAlert self = new InvitesNeededAlert( callbacks, state ); + callbacks.getDelegate().showDialogFragment( DlgID.DLG_INVITE, state ); + } + + private static void close( InvitesNeededAlert self ) + { + DbgUtils.assertOnUIThread(); + Assert.assertTrueNR( self == sInstance[0] ); + sInstance[0] = null; + if ( null != self.mAlert ) { + InviteChoicesAlert.dismissAny(); + self.mAlert.dismiss(); + } + } + + private InvitesNeededAlert( Callbacks callbacks, State state ) + { + mState = state; + mCallbacks = callbacks; + mDelegate = callbacks.getDelegate(); + DbgUtils.assertOnUIThread(); + Assert.assertTrueNR( null == sInstance[0] ); + sInstance[0] = this; + } + + private Dialog makeImpl( Callbacks callbacks, final DBAlert alert, + Object[] params ) + { + Dialog result = null; + State state = (State)params[0]; + mAlert = alert; + + Context context = mDelegate.getActivity(); + String title; + + boolean isRematch = state.isRematch; + if ( isRematch ) { + title = LocUtils.getString( context, R.string.waiting_rematch_title ); + } else { + title = LocUtils + .getQuantityString( context, R.plurals.waiting_title_fmt, + state.nPlayersMissing, state.nPlayersMissing ); + } + + String message = LocUtils + .getQuantityString( context, R.plurals.invite_msg_fmt, + state.nPlayersMissing, state.nPlayersMissing ); + message += "\n\n" + + LocUtils.getString( context, R.string.invite_msg_extra ); + + if ( isRematch ) { + message += "\n\n" + + LocUtils.getString( context, R.string.invite_msg_extra_rematch ); + } + + AlertDialog.Builder ab = mDelegate.makeAlertBuilder() + .setTitle( title ) + .setMessage( message ); + + alert.setNoDismissListenerPos( ab, R.string.newgame_invite, + new OnClickListener() { + @Override + public void onClick( DialogInterface dlg, int item ) { + onPosClick(); + } + } ); + + alert.setNoDismissListenerNeg( ab, R.string.button_close, + new OnClickListener() { + @Override + public void onClick( DialogInterface dlg, int item ) { + onNegClick(); + } + } ); + + result = ab.create(); + return result; + } + + private void onPosClick() + { + mCallbacks.onInviteClicked(); + } + + private void onNegClick() + { + Log.d( TAG, "onNegClick()" ); + mCallbacks.onCloseClicked(); + } +} diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/TransportProcs.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/TransportProcs.java index 419d60a4a..6db4b7c3c 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/TransportProcs.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/TransportProcs.java @@ -65,8 +65,8 @@ public interface TransportProcs { boolean relayNoConnProc( byte[] buf, String msgNo, String relayID ); public interface TPMsgHandler { - public void tpmRelayConnd( String room, int devOrder, boolean allHere, - int nMissing ); + // public void tpmRelayConnd( String room, int devOrder, boolean allHere, + // int nMissing ); public void tpmRelayErrorProc( XWRELAY_ERROR relayErr ); public void tpmCountChanged( int newCount ); } diff --git a/xwords4/android/app/src/main/res/layout/invite_view.xml b/xwords4/android/app/src/main/res/layout/invite_view.xml index 7afff687e..44f3ac4a4 100644 --- a/xwords4/android/app/src/main/res/layout/invite_view.xml +++ b/xwords4/android/app/src/main/res/layout/invite_view.xml @@ -91,6 +91,11 @@ android:layout_gravity="center_horizontal" android:paddingTop="10dp" /> + This game is waiting for one remote - player. Would you like to invite someone to join—assuming you - haven’t already? + player to respond to an invitation. This game is waiting for %1$d remote - players. Would you like to invite someone to join—assuming - you haven’t already? + players to repond to invitations. - - You have already invited a remote player to - this game. We are waiting for him/her to respond. Please - use the re-invite button if you think the invitation did not go - out. - You have already sent %1$d unique - invitations for this game. We are waiting for %2$d of the - recipients to respond. Please use the re-invite button if you - think the invitations did not go out. - - This game was created from an - invitation you received. As soon as it is able to connect to the - sender and any other invitees have arrived play will - begin. + + You will see this message until + all expected players have connected. If you’ve already sent an + invitation and suspect it was lost, there’s no harm in sending + another. + + With rematches, all + necessary invitations will have been sent automatically. But you + can always send new ones if an invitee is not responding. + + + + + + + + + + + + + + + Or just Tap to Invite -- if the other device also has Android Beaming and is nearby. @@ -2245,7 +2253,10 @@ to make more games visible.\n\n(If you later want to unhide the buttons go to the Appearance section of App settings). - Waiting for players + + Waiting for player + Waiting for %1$d players + Waiting for response Rematch in progress diff --git a/xwords4/common/server.c b/xwords4/common/server.c index da774ae1f..8143c3c8c 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -194,7 +194,8 @@ static XWStreamCtxt* messageStreamWithHeader( ServerCtxt* server, XWEnv xwe, XP_U16 devIndex, XW_Proto code ); static XP_Bool handleRegistrationMsg( ServerCtxt* server, XWEnv xwe, XWStreamCtxt* stream ); -static XP_S8 registerRemotePlayer( ServerCtxt* server, XWStreamCtxt* stream ); +static XP_S8 registerRemotePlayer( ServerCtxt* server, XWEnv xwe, + XWStreamCtxt* stream ); static void sendInitialMessage( ServerCtxt* server, XWEnv xwe ); static void sendBadWordMsgs( ServerCtxt* server, XWEnv xwe ); static XP_Bool handleIllegalWord( ServerCtxt* server, XWEnv xwe, @@ -914,7 +915,7 @@ handleRegistrationMsg( ServerCtxt* server, XWEnv xwe, XWStreamCtxt* stream ) XP_S8 prevIndex = -1; #endif for ( ; ii < playersInMsg; ++ii ) { - clientIndex = registerRemotePlayer( server, stream ); + clientIndex = registerRemotePlayer( server, xwe, stream ); if ( -1 == clientIndex ) { success = XP_FALSE; break; @@ -1790,7 +1791,7 @@ findFirstPending( ServerCtxt* server, ServerPlayer** playerP ) } /* findFirstPending */ static XP_S8 -registerRemotePlayer( ServerCtxt* server, XWStreamCtxt* stream ) +registerRemotePlayer( ServerCtxt* server, XWEnv xwe, XWStreamCtxt* stream ) { XP_S8 deviceIndex = -1; XP_PlayerAddr channelNo; @@ -1839,6 +1840,8 @@ registerRemotePlayer( ServerCtxt* server, XWStreamCtxt* stream ) } player->deviceIndex = deviceIndex; + + informMissing( server, xwe ); } return deviceIndex; } /* registerRemotePlayer */