toward rotation-safe invite dialog

Rewrite the overly-complex invite dialog that's posted when a game opens
and is missing players so that it doesn't crash after an orientation
change. It's still possible to dismiss it yet have the game stay open.
The goal is that a game in that state always has a dialog on top of it
so you don't get confused about e.g. why you don't have tiles, but
there's more work to be done there.

Also added a common superclass for all my DialogFragments. It may be
useful e.g. for having each DelegateBase instance able to track what
dialogs it currently has up, e.g. to have a policy about how many of the
same class can be live. I'm thinking here of how to prevent a
robot-vs-robot game from ending with a stack of 25 move reports. (The
change here is that the old activity.showDialog(int) only allowed one at
a time, and some of my logic takes that for granted.)
This commit is contained in:
Eric House 2017-03-07 07:48:46 -08:00
parent 0980bcfbe5
commit 618f9cf20a
10 changed files with 256 additions and 170 deletions

View file

@ -36,7 +36,7 @@ import junit.framework.Assert;
import org.eehouse.android.xw4.loc.LocUtils;
public class AboutAlert extends DialogFragment {
public class AboutAlert extends XWDialogFragment {
public static AboutAlert newInstance()
{

View file

@ -24,10 +24,10 @@ import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
@ -42,6 +42,7 @@ import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
@ -73,6 +74,7 @@ import org.eehouse.android.xw4.jni.UtilCtxt;
import org.eehouse.android.xw4.jni.UtilCtxtImpl;
import org.eehouse.android.xw4.jni.XwJNI.GamePtr;
import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.loc.LocUtils;
public class BoardDelegate extends DelegateBase
implements TransportProcs.TPMsgHandler, View.OnClickListener,
@ -166,7 +168,7 @@ public class BoardDelegate extends DelegateBase
private DBAlert.OnDismissListener m_blockingODL =
new DBAlert.OnDismissListener() {
public void onDismissed() {
public void onDismissed( XWDialogFragment frag ) {
releaseIfBlocking();
}
};
@ -267,7 +269,7 @@ public class BoardDelegate extends DelegateBase
}
break;
case DLG_DELETED:
case DLG_DELETED: {
ab = ab.setTitle( R.string.query_title )
.setMessage( R.string.msg_dev_deleted )
.setPositiveButton( android.R.string.ok, null );
@ -279,6 +281,7 @@ public class BoardDelegate extends DelegateBase
};
ab.setNegativeButton( R.string.button_delete, lstnr );
dialog = ab.create();
}
break;
case QUERY_TRADE:
@ -355,7 +358,6 @@ public class BoardDelegate extends DelegateBase
}
dialog = ab.create();
alert.setOnDismissListener( m_blockingODL );
}
break;
@ -454,9 +456,20 @@ public class BoardDelegate extends DelegateBase
.create();
break;
case DLG_INVITE:
dialog = makeAlertDialog();
InviteAlertState state = (InviteAlertState)params[0];
dialog = makeInviteDialog( alert, state );
if ( null != dialog ) {
m_inviteAlert = alert;
alert.setOnDismissListener( new DBAlert.OnDismissListener() {
@Override
public void onDismissed( XWDialogFragment frag ) {
DbgUtils.logd( TAG, "onDismissed(%s)", frag.toString() );
m_inviteAlert = null;
// if ( m_nMissing > 0 ) {
// finish();
// }
}
});
}
break;
@ -472,137 +485,154 @@ public class BoardDelegate extends DelegateBase
return dialog;
} // makeDialog
private Dialog makeAlertDialog()
{
String message;
int titleID;
boolean showInviteButton = true;
boolean showNeutButton = false;
int buttonTxt = R.string.newgame_invite;
if ( m_relayMissing ) {
titleID = R.string.seeking_relay;
// If relay is only means, don't allow at all
boolean relayOnly = 1 >= m_connTypes.size();
showInviteButton = !relayOnly;
message = getString( R.string.no_relay_conn );
if ( NetStateCache.netAvail( m_activity )
&& NetStateCache.onWifi() ) {
message += getString( R.string.wifi_warning );
}
if ( !relayOnly ) {
CommsConnTypeSet without = (CommsConnTypeSet)
m_connTypes.clone();
without.remove( CommsConnType.COMMS_CONN_RELAY );
message += "\n\n"
+ getString( R.string.drop_relay_warning_fmt,
without.toString( m_activity, true ) );
buttonTxt = R.string.newgame_drop_relay;
}
} else {
m_sentInfo = DBUtils.getInvitesFor( m_activity, m_rowid );
int nSent = m_sentInfo.getMinPlayerCount();
boolean invitesSent = nSent >= m_nMissing;
if ( invitesSent ) {
if ( m_summary.hasRematchInfo() ) {
titleID = R.string.waiting_rematch_title;
message = getString( R.string.rematch_msg );
} else {
titleID = R.string.waiting_invite_title;
message = getQuantityString( R.plurals.invite_sent_fmt,
nSent, nSent, m_nMissing );
}
buttonTxt = R.string.button_reinvite;
showNeutButton = true;
} else if ( DeviceRole.SERVER_ISCLIENT == m_gi.serverRole ) {
Assert.assertFalse( m_summary.hasRematchInfo() );
message = getString( R.string.invited_msg );
titleID = R.string.waiting_title;
showInviteButton = false;
} else {
titleID = R.string.waiting_title;
message = getQuantityString( R.plurals.invite_msg_fmt,
m_nMissing, m_nMissing );
}
if ( ! invitesSent && showInviteButton ) {
String ps = null;
if ( m_nMissing > 1 ) {
ps = getString( R.string.invite_multiple );
} else {
boolean[] avail = NFCUtils.nfcAvail( m_activity );
if ( avail[1] ) {
ps = getString( R.string.invite_if_nfc );
}
}
if ( null != ps ) {
message += "\n\n" + ps;
}
}
message += "\n\n" + getString( R.string.invite_stays );
}
// Button button = ad.getButton( AlertDialog.BUTTON_POSITIVE );
// button.setVisibility( nukeInviteButton ? View.GONE : View.VISIBLE );
// if ( !nukeInviteButton ) {
// button.setText( buttonTxt );
// }
// button = ad.getButton( AlertDialog.BUTTON_NEUTRAL );
// button.setVisibility( nukeNeutButton ? View.GONE : View.VISIBLE );
OnClickListener lstnr = new OnClickListener() {
public void onClick( DialogInterface dialog, int item ){
if ( !m_relayMissing ||
! m_connTypes.contains(CommsConnType.COMMS_CONN_RELAY) ) {
Assert.assertTrue( 0 < m_nMissing );
if ( m_summary.hasRematchInfo() ) {
tryRematchInvites( true );
} else {
callInviteChoices( m_sentInfo );
}
} else {
askDropRelay();
}
}
};
AlertDialog.Builder ab = makeAlertBuilder()
.setTitle( titleID )
.setMessage( message )
.setPositiveButton( buttonTxt, lstnr )
.setOnCancelListener( new OnCancelListener() {
public void onCancel( DialogInterface dialog ) {
finish();
}
} );
if ( showNeutButton ) {
OnClickListener lstnrMore = new OnClickListener() {
public void onClick( DialogInterface dialog,
int item ) {
String msg = m_sentInfo
.getAsText( m_activity );
makeOkOnlyBuilder( msg ).show();
}
};
ab.setNeutralButton( R.string.newgame_invite_more, lstnrMore );
}
if ( showInviteButton ) {
OnClickListener lstnrWait = new OnClickListener() {
public void onClick( DialogInterface dialog,
int item ) {
finish();
}
};
ab.setNegativeButton( R.string.button_wait, lstnrWait );
}
Dialog dialog = ab.create();
return dialog;
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 ) {
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 );
}
// Button button = ad.getButton( AlertDialog.BUTTON_POSITIVE );
// button.setVisibility( nukeInviteButton ? View.GONE : View.VISIBLE );
// if ( !nukeInviteButton ) {
// button.setText( buttonTxt );
// }
// button = ad.getButton( AlertDialog.BUTTON_NEUTRAL );
// button.setVisibility( nukeNeutButton ? View.GONE : View.VISIBLE );
OnClickListener lstnr = new OnClickListener() {
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 {
callInviteChoices( sentInfo[0] );
}
} else {
askDropRelay();
}
}
};
AlertDialog.Builder ab = makeAlertBuilder()
.setTitle( titleID )
.setMessage( message )
.setPositiveButton( buttonTxt, lstnr )
.setOnCancelListener( new OnCancelListener() {
public void onCancel( DialogInterface dialog ) {
finish();
}
} );
if ( showNeutButton ) {
OnClickListener lstnrMore = new OnClickListener() {
public void onClick( DialogInterface dialog,
int item ) {
String msg = sentInfo[0].getAsText( activity );
makeOkOnlyBuilder( msg ).show();
}
};
ab.setNeutralButton( R.string.newgame_invite_more, lstnrMore );
}
if ( showInviteButton ) {
OnClickListener lstnrWait = new OnClickListener() {
public void onClick( DialogInterface dialog,
int item ) {
finish();
}
};
ab.setNegativeButton( R.string.button_wait, lstnrWait );
}
dialog = ab.create();
}
return dialog;
} // makeInviteDialog
public BoardDelegate( Delegator delegator, Bundle savedInstanceState )
{
super( delegator, savedInstanceState, R.layout.board, R.menu.board_menu );
@ -757,8 +787,8 @@ public class BoardDelegate extends DelegateBase
// in case of change...
setBackgroundColor();
setKeepScreenOn();
} else if ( 0 < m_nMissing ) {
showInviteAlert();
} else {
showInviteAlertIf();
}
}
}
@ -1197,6 +1227,11 @@ public class BoardDelegate extends DelegateBase
case DELETE_AND_EXIT:
finish();
break;
case LAUNCH_INVITE_ACTION:
showInviteAlertIf();
break;
default:
handled = false;
}
@ -1663,7 +1698,7 @@ public class BoardDelegate extends DelegateBase
skipDismiss = !tryRematchInvites( false );
} else if ( !m_haveInvited ) {
m_haveInvited = true;
showInviteAlert();
showInviteAlertIf();
invalidateOptionsMenuIf();
skipDismiss = true;
} else {
@ -1981,7 +2016,7 @@ public class BoardDelegate extends DelegateBase
post( new Runnable() {
@Override
public void run() {
showInviteAlert();
showInviteAlertIf();
}
} );
}
@ -2292,8 +2327,6 @@ public class BoardDelegate extends DelegateBase
if ( null != m_inviteAlert ) {
DbgUtils.logd( TAG, "dismissing invite alert" );
m_inviteAlert.dismiss();
m_inviteAlert = null;
// m_showedInviteAlert = false;
}
}
}
@ -2421,11 +2454,18 @@ public class BoardDelegate extends DelegateBase
} );
}
private void showInviteAlert()
private void showInviteAlertIf()
{
if ( !m_showedInviteAlert && null == m_inviteAlert ) {
if ( null == m_inviteAlert & m_nMissing > 0 ) {
m_showedInviteAlert = true;
showDialogFragment( DlgID.DLG_INVITE );
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_nMissing;
showDialogFragment( DlgID.DLG_INVITE, ias );
}
}

View file

@ -33,19 +33,13 @@ import java.io.Serializable;
import junit.framework.Assert;
public class DBAlert extends DialogFragment {
public class DBAlert extends XWDialogFragment {
private static final String TAG = DBAlert.class.getSimpleName();
private static final String DLG_ID_KEY = "DLG_ID_KEY";
private static final String PARMS_KEY = "PARMS_KEY";
public interface OnDismissListener {
void onDismissed();
}
private Object[] mParams;
private DlgID mDlgID;
private OnDismissListener m_onDismiss;
public static DBAlert newInstance( DlgID dlgID, Object[] params )
{
if ( BuildConfig.DEBUG ) {
@ -78,15 +72,6 @@ public class DBAlert extends DialogFragment {
bundle.putSerializable( PARMS_KEY, mParams );
}
@Override
public void onDismiss( DialogInterface dif )
{
if ( null != m_onDismiss ) {
m_onDismiss.onDismissed();
}
super.onDismiss( dif );
}
@Override
public Dialog onCreateDialog( Bundle sis )
{
@ -126,8 +111,4 @@ public class DBAlert extends DialogFragment {
return dialog;
}
protected void setOnDismissListener( OnDismissListener lstnr )
{
m_onDismiss = lstnr;
}
}

View file

@ -66,6 +66,7 @@ public class DelegateBase implements DlgClickNotify,
private Activity m_activity;
private int m_optionsMenuID;
private int m_layoutID;
private boolean m_finishCalled;
private View m_rootView;
private boolean m_isVisible;
private ArrayList<Runnable> m_visibleProcs = new ArrayList<Runnable>();
@ -297,7 +298,10 @@ public class DelegateBase implements DlgClickNotify,
if ( m_activity instanceof MainActivity ) {
MainActivity main = (MainActivity)m_activity;
if ( main.inDPMode() ) {
main.finishFragment();
if ( !m_finishCalled ) {
m_finishCalled = true;
main.finishFragment();
}
handled = true;
}
}

View file

@ -38,7 +38,7 @@ import org.eehouse.android.xw4.loc.LocUtils;
/** Abstract superclass for Alerts that have moved from and are still created
* inside DlgDelegate
*/
public class DlgDelegateAlert extends DialogFragment {
public class DlgDelegateAlert extends XWDialogFragment {
private static final String TAG = DlgDelegateAlert.class.getSimpleName();
private static final String STATE_KEY = "STATE_KEY";
private DlgState m_state;

View file

@ -142,6 +142,21 @@ public class DualpaneDelegate extends DelegateBase {
return handled;
}
@Override
public boolean onDismissed( Action action, Object[] params )
{
boolean handled = false;
MainActivity main = (MainActivity)m_activity;
XWFragment[] frags = main.getVisibleFragments();
for ( XWFragment frag : frags ) {
handled = frag.getDelegate().onDismissed( action, params );
if ( handled ) {
break;
}
}
return handled;
}
@Override
public void inviteChoiceMade( Action action, InviteMeans means,
Object[] params )

View file

@ -212,7 +212,7 @@ public class GameConfigDelegate extends DelegateBase
.create();
alert.setOnDismissListener( new DBAlert.OnDismissListener() {
@Override
public void onDismissed() {
public void onDismissed( XWDialogFragment frag ) {
if ( m_gi.forceRemoteConsistent() ) {
showToast( R.string.forced_consistent );
loadPlayersList();
@ -251,7 +251,7 @@ public class GameConfigDelegate extends DelegateBase
alert.setOnDismissListener( new DBAlert.OnDismissListener() {
@Override
public void onDismissed() {
public void onDismissed( XWDialogFragment frag ) {
closeNoSave();
}
} );

View file

@ -827,7 +827,7 @@ public class GamesListDelegate extends ListDelegateBase
0, true ) );
alert.setOnDismissListener( new DBAlert.OnDismissListener() {
@Override
public void onDismissed() {
public void onDismissed( XWDialogFragment frag ) {
String name = etext.getText().toString();
if ( 0 == name.length() ) {
name = CommonPrefs.

View file

@ -30,7 +30,7 @@ import junit.framework.Assert;
import org.eehouse.android.xw4.loc.LocUtils;
public class LookupAlert extends DialogFragment {
public class LookupAlert extends XWDialogFragment {
private LookupAlertView m_view;
public static LookupAlert newInstance( String[] words, int lang, boolean noStudy )

View file

@ -0,0 +1,46 @@
/* -*- compile-command: "find-and-gradle.sh insXw4Debug"; -*- */
/*
* Copyright 2017 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.support.v4.app.DialogFragment;
import android.content.DialogInterface;
class XWDialogFragment extends DialogFragment {
private OnDismissListener m_onDismiss;
public interface OnDismissListener {
void onDismissed( XWDialogFragment frag );
}
@Override
public void onDismiss( DialogInterface dif )
{
if ( null != m_onDismiss ) {
m_onDismiss.onDismissed( this );
}
super.onDismiss( dif );
}
protected void setOnDismissListener( OnDismissListener lstnr )
{
m_onDismiss = lstnr;
}
}