improve UI around invites and offering QR code

This commit is contained in:
Eric House 2020-10-05 18:09:54 -07:00
parent 8957b3ff53
commit 94a709423a
16 changed files with 53 additions and 642 deletions

View file

@ -1371,12 +1371,9 @@ public class BoardDelegate extends DelegateBase
info, perms );
break;
case RELAY:
RelayInviteDelegate.launchForResult( m_activity, m_mySIS.nMissing, info,
RequestCode.RELAY_INVITE_RESULT );
break;
case MQTT:
MQTTInviteDelegate.launchForResult( m_activity, m_mySIS.nMissing, info,
RequestCode.MQTT_INVITE_RESULT );
// These have been removed as options
Assert.failDbg();
break;
case WIFIDIRECT:
WiDirInviteDelegate.launchForResult( m_activity,

View file

@ -57,9 +57,9 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class DelegateBase implements DlgClickNotify,
DlgDelegate.HasDlgDelegate,
MultiService.MultiEventListener {
public abstract class DelegateBase implements DlgClickNotify,
DlgDelegate.HasDlgDelegate,
MultiService.MultiEventListener {
private static final String TAG = DelegateBase.class.getSimpleName();
private DlgDelegate m_dlgDelegate;
@ -94,7 +94,7 @@ public class DelegateBase implements DlgClickNotify,
public Activity getActivity() { return m_activity; }
// Does nothing unless overridden. These belong in an interface.
protected void init( Bundle savedInstanceState ) { Assert.failDbg(); }
protected abstract void init( Bundle savedInstanceState );
protected void onSaveInstanceState( Bundle outState ) {}
public boolean onPrepareOptionsMenu( Menu menu ) { return false; }
public boolean onOptionsItemSelected( MenuItem item ) { return false; }

View file

@ -1,298 +0,0 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2012 - 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.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Button;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Collections;
import java.util.Set;
import java.util.Iterator;
import org.eehouse.android.xw4.DlgDelegate.Action;
abstract class DevIDInviteDelegate extends InviteDelegate {
private static final String TAG = DevIDInviteDelegate.class.getSimpleName();
private static int[] BUTTONIDS = {
R.id.button_relay_add,
R.id.manual_add_button,
R.id.button_clear,
R.id.button_edit,
};
protected ArrayList<DevIDRec> m_devIDRecs;
private Activity m_activity;
private boolean m_immobileConfirmed; // WTF is this?
abstract String getRecsKey();
abstract String getMeDevID();
public DevIDInviteDelegate( Delegator delegator, Bundle savedInstanceState )
{
super( delegator, savedInstanceState );
m_activity = delegator.getActivity();
}
static class DevIDRec implements InviterItem, Serializable {
public String m_devID;
public String m_opponent;
public int m_nPlayers;
public DevIDRec( String opponent, String devID )
{
m_devID = devID;
m_nPlayers = 1;
m_opponent = opponent;
}
public String getDev() { return m_devID; }
public boolean equals( InviterItem item )
{
return item != null
&& ((DevIDRec)item).m_devID == m_devID;
}
}
void saveAndRebuild()
{
DBUtils.setSerializableFor( m_activity, getRecsKey(), m_devIDRecs );
rebuildList( false );
}
void rebuildList( boolean checkIfAll )
{
Collections.sort( m_devIDRecs, new Comparator<DevIDRec>() {
public int compare( DevIDRec rec1, DevIDRec rec2 ) {
return rec1.m_opponent.compareTo(rec2.m_opponent);
}
});
addSelf();
updateList( m_devIDRecs );
tryEnable();
}
@Override
protected void init( Bundle savedInstanceState )
{
super.init( savedInstanceState );
String msg = getString( R.string.button_invite );
msg = getQuantityString( R.plurals.invite_relay_desc_fmt, m_nMissing,
m_nMissing, msg );
super.init( msg, R.string.empty_relay_inviter );
addButtonBar( R.layout.relay_buttons, BUTTONIDS );
getSavedState();
rebuildList( true );
}
@Override
protected void onBarButtonClicked( int id )
{
switch( id ) {
case R.id.button_relay_add:
Utils.notImpl( getActivity() );
break;
case R.id.manual_add_button:
showDialogFragment( DlgID.GET_NUMBER );
break;
case R.id.button_clear:
int count = getChecked().size();
String msg = getQuantityString( R.plurals.confirm_clear_relay_fmt,
count, count );
makeConfirmThenBuilder( msg, Action.CLEAR_ACTION ).show();
break;
case R.id.button_edit:
Object obj = getChecked().iterator().next();
Log.d( TAG, "passing %s", obj );
showDialogFragment( DlgID.GET_NUMBER, obj );
break;
}
}
@Override
protected Dialog makeDialog( DBAlert alert, Object[] params )
{
Dialog dialog = null;
DialogInterface.OnClickListener lstnr;
switch( alert.getDlgID() ) {
case GET_NUMBER: {
final DevIDRec curRec =
1 <= params.length && params[0] instanceof String
? getHasID((String)params[0]) : null;
Log.d( TAG, "curRec: %s", curRec );
final View getNumView = inflate( R.layout.get_relay );
final EditText numField = (EditText)
getNumView.findViewById( R.id.num_field );
final EditText nameField = (EditText)
getNumView.findViewById( R.id.name_field );
if ( null != curRec ) {
numField.setText( curRec.m_devID );
nameField.setText( curRec.m_opponent );
}
lstnr = new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlg, int item ) {
String number = numField.getText().toString();
if ( null != number && 0 < number.length() ) {
String name = nameField.getText().toString();
if ( curRec != null ) {
curRec.m_opponent = name;
curRec.m_devID = number;
} else {
DevIDRec rec = new DevIDRec( name, number );
m_devIDRecs.add( rec );
clearChecked();
onItemChecked( rec, true );
}
saveAndRebuild();
}
}
};
dialog = makeAlertBuilder()
.setTitle( R.string.get_sms_title )
.setView( getNumView )
.setPositiveButton( android.R.string.ok, lstnr )
.setNegativeButton( android.R.string.cancel, null )
.create();
}
break;
default:
dialog = super.makeDialog( alert, params );
break;
}
return dialog;
}
@Override
protected void onChildAdded( View child, InviterItem data )
{
DevIDRec rec = (DevIDRec)data;
((TwoStrsItem)child).setStrings( rec.m_opponent, rec.m_devID );
}
@Override
protected void tryEnable()
{
super.tryEnable();
Button button = (Button)findViewById( R.id.button_clear );
if ( null != button ) { // may not be there yet
button.setEnabled( 0 < getChecked().size() );
}
button = (Button)findViewById( R.id.button_edit );
if ( null != button ) {
button.setEnabled( 1 == getChecked().size() );
}
}
// DlgDelegate.DlgClickNotify interface
@Override
public boolean onPosButton( Action action, Object[] params )
{
boolean handled = true;
switch( action ) {
case CLEAR_ACTION:
clearSelectedImpl();
break;
case USE_IMMOBILE_ACTION:
m_immobileConfirmed = true;
break;
default:
handled = super.onPosButton( action, params );
break;
}
return handled;
}
@Override
public boolean onDismissed( Action action, Object[] params )
{
boolean handled = true;
if ( Action.USE_IMMOBILE_ACTION == action && m_immobileConfirmed ) {
makeConfirmThenBuilder( R.string.warn_unlimited,
Action.POST_WARNING_ACTION )
.setPosButton( R.string.button_yes )
.show();
} else {
handled = false;
}
return handled;
}
void addSelf()
{
boolean hasSelf = false;
String me = getMeDevID();
for ( DevIDRec rec : m_devIDRecs ) {
if ( rec.m_devID.equals( me ) ) {
hasSelf = true;
break;
}
}
if ( !hasSelf ) {
DevIDRec rec = new DevIDRec( "self", me );
m_devIDRecs.add( rec );
}
}
void getSavedState()
{
m_devIDRecs = (ArrayList<DevIDRec>)DBUtils.getSerializableFor( m_activity, getRecsKey() );
if ( null == m_devIDRecs ) {
m_devIDRecs = new ArrayList<>();
}
}
void clearSelectedImpl()
{
Set<String> checked = getChecked();
for ( Iterator<DevIDRec> iter = m_devIDRecs.iterator(); iter.hasNext(); ) {
if ( checked.contains( iter.next().getDev() ) ) {
iter.remove();
}
}
clearChecked();
saveAndRebuild();
}
DevIDRec getHasID( String id )
{
DevIDRec result = null;
for ( DevIDRec rec : m_devIDRecs ) {
if ( rec.m_devID.equals(id) ) {
result = rec;
break;
}
}
return result;
}
}

View file

@ -333,19 +333,24 @@ public class DlgDelegate {
// These are stored in the INVITES table. Don't change order
// gratuitously
public static enum InviteMeans {
SMS_DATA(R.string.invite_choice_data_sms), // classic NBS-based data sms
EMAIL(R.string.invite_choice_email),
NFC(R.string.invite_choice_nfc),
BLUETOOTH(R.string.invite_choice_bt),
CLIPBOARD(R.string.slmenu_copy_sel),
RELAY(R.string.invite_choice_relay),
WIFIDIRECT(R.string.invite_choice_p2p),
SMS_USER(R.string.invite_choice_user_sms), // just launch the SMS app, as with email
MQTT(R.string.invite_choice_mqtt);
SMS_DATA(R.string.invite_choice_data_sms, false), // classic NBS-based data sms
EMAIL(R.string.invite_choice_email, false),
NFC(R.string.invite_choice_nfc, true),
BLUETOOTH(R.string.invite_choice_bt, true),
CLIPBOARD(R.string.slmenu_copy_sel, false),
RELAY(R.string.invite_choice_relay, false),
WIFIDIRECT(R.string.invite_choice_p2p, false),
SMS_USER(R.string.invite_choice_user_sms, false), // just launch the SMS app, as with email
MQTT(R.string.invite_choice_mqtt, false);
private InviteMeans( int resid) { mResID = resid; }
private InviteMeans( int resid, boolean local) {
mResID = resid;
mIsLocal = local;
}
private int mResID;
private boolean mIsLocal;
public int getUserDescID() { return mResID; }
public boolean isForLocal() { return mIsLocal; }
};
boolean onPosButton( Action action, Object... params );

View file

@ -90,17 +90,11 @@ public class InviteChoicesAlert extends DlgDelegateAlert
means.add( InviteMeans.EMAIL );
means.add( InviteMeans.SMS_USER );
if ( BTService.BTAvailable() ) {
means.add( InviteMeans.BLUETOOTH );
}
if ( Utils.deviceSupportsNBS(context) ) {
means.add( InviteMeans.SMS_DATA );
}
if ( BuildConfig.NON_RELEASE ) {
means.add( InviteMeans.RELAY );
}
if ( BuildConfig.NON_RELEASE && BuildConfig.OFFER_MQTT ) {
means.add( InviteMeans.MQTT );
if ( BTService.BTAvailable() ) {
means.add( InviteMeans.BLUETOOTH );
}
if ( WiDirWrapper.enabled() ) {
means.add( InviteMeans.WIFIDIRECT );

View file

@ -83,13 +83,16 @@ public class InviteView extends ScrollView
mGroupHow = (RadioGroup)findViewById( R.id.group_how );
mGroupHow.setOnCheckedChangeListener( this );
final View divider = mGroupHow.findViewById( R.id.local_divider );
for ( InviteMeans means : meansList ) {
Assert.assertNotNull( means );
RadioButton button = (RadioButton)LocUtils
.inflate( context, R.layout.invite_radio );
button.setText( LocUtils.getString( context, means.getUserDescID() ) );
// -2: place before QR code and its explanatory text
int where = mGroupHow.getChildCount() - 2;
int where = means.isForLocal()
// -2: place before QR code and its explanatory text
? mGroupHow.getChildCount() - 2
: mGroupHow.indexOfChild( divider );
mGroupHow.addView( button, where );
mHowMeans.put( button, means );
}

View file

@ -25,7 +25,7 @@ import android.view.View;
import android.widget.ListAdapter;
import android.widget.ListView;
public class ListDelegateBase extends DelegateBase {
public abstract class ListDelegateBase extends DelegateBase {
private Activity m_activity;
private Delegator m_delegator;

View file

@ -1,34 +0,0 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2015 - 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.os.Bundle;
public class MQTTInviteActivity extends InviteActivity {
private MQTTInviteDelegate m_dlgt;
@Override
protected void onCreate( Bundle savedInstanceState )
{
m_dlgt = new MQTTInviteDelegate( this, savedInstanceState );
super.onCreate( savedInstanceState, m_dlgt );
}
}

View file

@ -1,68 +0,0 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2012 - 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.Activity;
import android.content.Intent;
import android.os.Bundle;
import org.eehouse.android.xw4.DBUtils.SentInvitesInfo;
import org.eehouse.android.xw4.jni.XwJNI;
public class MQTTInviteDelegate extends DevIDInviteDelegate {
private static final String TAG = MQTTInviteDelegate.class.getSimpleName();
private static final String RECS_KEY = TAG + "/recs";
private static final boolean MQTTINVITE_SUPPORTED = BuildConfig.NON_RELEASE;
private String m_devIDStr;
public static void launchForResult( Activity activity, int nMissing,
SentInvitesInfo info,
RequestCode requestCode )
{
if ( MQTTINVITE_SUPPORTED ) {
Intent intent =
InviteDelegate.makeIntent( activity, MQTTInviteActivity.class,
nMissing, info );
activity.startActivityForResult( intent, requestCode.ordinal() );
}
}
public MQTTInviteDelegate( Delegator delegator, Bundle savedInstanceState )
{
super( delegator, savedInstanceState );
}
@Override
String getRecsKey()
{
return RECS_KEY;
}
@Override
String getMeDevID()
{
if ( null == m_devIDStr ) {
m_devIDStr = XwJNI.dvc_getMQTTDevID(null);
}
return m_devIDStr;
}
}

View file

@ -1,34 +0,0 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2015 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.os.Bundle;
public class RelayInviteActivity extends InviteActivity {
private RelayInviteDelegate m_dlgt;
@Override
protected void onCreate( Bundle savedInstanceState )
{
m_dlgt = new RelayInviteDelegate( this, savedInstanceState );
super.onCreate( savedInstanceState, m_dlgt );
}
}

View file

@ -1,68 +0,0 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2012 - 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.Activity;
import android.content.Intent;
import android.os.Bundle;
import org.eehouse.android.xw4.DBUtils.SentInvitesInfo;
public class RelayInviteDelegate extends DevIDInviteDelegate {
private static final String TAG = RelayInviteDelegate.class.getSimpleName();
private static final String RECS_KEY = TAG + "/recs";
private static final boolean RELAYINVITE_SUPPORTED = BuildConfig.NON_RELEASE;
private boolean m_immobileConfirmed;
private String mRelayDevIDStr;
public static void launchForResult( Activity activity, int nMissing,
SentInvitesInfo info,
RequestCode requestCode )
{
if ( RELAYINVITE_SUPPORTED ) {
Intent intent =
InviteDelegate.makeIntent( activity, RelayInviteActivity.class,
nMissing, info );
activity.startActivityForResult( intent, requestCode.ordinal() );
}
}
public RelayInviteDelegate( Delegator delegator, Bundle savedInstanceState )
{
super( delegator, savedInstanceState );
}
@Override
String getRecsKey()
{
return RECS_KEY;
}
@Override
String getMeDevID()
{
if ( null == mRelayDevIDStr ) {
mRelayDevIDStr = String.format( "%d", DevID.getRelayDevIDInt(getActivity()) );
}
return mRelayDevIDStr;
}
}

View file

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView android:text="@string/get_relay_name"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<EditText android:id="@+id/name_field"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_marginLeft="30dip"
android:layout_marginRight="30dip"
android:autoText="false"
android:maxLines="1"
android:inputType="text"
android:selectAllOnFocus="true"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<TextView android:text="@string/get_relay_number"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<EditText android:id="@+id/num_field"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_marginLeft="30dip"
android:layout_marginRight="30dip"
android:autoText="false"
android:maxLines="1"
android:inputType="text"
android:selectAllOnFocus="true"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
</LinearLayout>

View file

@ -54,13 +54,21 @@
android:layout_height="wrap_content"
>
<!-- RadioButtons will be inserted above this item. -->
<!-- RadioButtons will be inserted relative to this item -->
<TextView android:id="@+id/local_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/local_invite_summary"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<!-- intro for QR code: these two elems stay at the bottom -->
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/qrcode_invite_summary"
android:textAppearance="?android:attr/textAppearanceMedium"
android:padding="5dp"
/>
/>
<ImageView android:id="@+id/qr_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View file

@ -39,6 +39,17 @@
android:padding="20dp"
/>
<Button android:id="@+id/button_invite"
android:text="@string/button_invite"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<FrameLayout android:id="@+id/button_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<LinearLayout android:id="@+id/progress_line"
android:orientation="vertical"
android:layout_width="match_parent"
@ -61,17 +72,5 @@
/>
</LinearLayout>
<Button android:id="@+id/button_invite"
android:text="@string/button_invite"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<FrameLayout android:id="@+id/button_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
</ScrollView>

View file

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<Button android:id="@+id/button_relay_add"
android:text="@string/button_relay_add"
style="@style/evenly_spaced_horizontal"
/>
<ImageButton android:id="@+id/manual_add_button"
android:layout_height="fill_parent"
android:layout_width="wrap_content"
android:src="@android:drawable/ic_input_add"
android:clickable="false"
/>
<Button android:id="@+id/button_clear"
android:text="@string/bt_pick_clear_button"
style="@style/evenly_spaced_horizontal"
/>
<Button android:id="@+id/button_edit"
android:text="@string/button_edit"
style="@style/evenly_spaced_horizontal"
/>
</LinearLayout>

View file

@ -965,10 +965,6 @@
<!-- explanation of the above -->
<string name="network_behavior_summary">Settings that apply to
networked games</string>
<string name="relay_behavior">Relay play settings</string>
<!-- explanation of the above -->
<string name="relay_behavior_summary">Settings that apply to
internet-connected games </string>
<string name="disable_relay">Disable play via the relay </string>
<string name="disable_relay_summary">Disable all internet communication</string>
<!--
@ -1040,7 +1036,9 @@
<!-- Text shown only when there are not any previous opponents to invite -->
<string name="invite_who_empty_expl">empty expl</string>
<!-- Shown above QR code in the how-to-invite dialog -->
<string name="qrcode_invite_summary">Or just have your opponent scan this QRCode</string>
<string name="qrcode_invite_summary">QRCode your opponent can scan</string>
<!-- Shown in the middle of the invite-how radio buttons -->
<string name="local_invite_summary">If your opponent is nearby…</string>
<!-- Radio button in invite choices dialog for list ways to send an invitation -->
<string name="radio_how">How?</string>
@ -1734,19 +1732,12 @@
<!-- -->
<string name="button_sms_add">Import contact</string>
<!-- -->
<string name="button_relay_add">Scan games</string>
<!-- -->
<plurals name="invite_sms_desc_fmt">
<item quantity="one">Please check the phone number you want to
invite to your new game, then tap “%2$s”.</item>
<item quantity="other">Please check the %1$d phone numbers you
want to invite to your new game, then tap “%2$s”.</item>
</plurals>
<!-- Appears near top of invitation phone number picker when it's
the user-visible SMS case -->
<string name="invite_sms_desc">Your SMS messaging app will then be
launched with a message for you to send to the phone number
youve selected.</string>
<!-- Appears near top of invitation phone number picker when it's
the user-invisible Data SMS case -->
<string name="invite_nbs_desc">A data message will then be
@ -1784,17 +1775,11 @@
<string name="empty_p2p_inviter">There are currently no devices
reachable via WiFiDirect that have CrossWords installed.</string>
<!-- -->
<string name="empty_relay_inviter">This list of devices is
empty. Use the “Scan games” button to scan your old games
for opponents. Use the “+” button to enter device IDs directly.</string>
<!-- -->
<string name="get_sms_title">Manual entry</string>
<string name="get_sms_number">Device phone number:</string>
<string name="get_sms_name">Contact name (optional):</string>
<string name="get_relay_name">Device name (optional):</string>
<!-- hint text in password-setting text area -->
<string name="password_hint">(Optional)</string>
<string name="get_relay_number">Enter device ID:</string>
<!-- -->
<plurals name="confirm_clear_sms_fmt">
<item quantity="one">Are you sure you want to delete the checked
@ -2318,7 +2303,6 @@
<string name="radio_name_tablet">Tablet/no radio</string>
<string name="radio_name_gsm">GSM</string>
<string name="radio_name_cdma">CDMA</string>
<string name="nfc_to_self">Send via NFC to self?</string>
<string name="game_summary_field_rowid">rowid</string>
<string name="game_summary_field_gameid">gameid</string>
<string name="game_summary_field_npackets">Pending packet count</string>
@ -2396,8 +2380,6 @@
<string name="confirm_clear_chat">Are you sure you want to delete
all chat history for this game?\n\n(This action cannot be
undone.)</string>
<string name="rel_invite_title">Relay invite title</string>
<string name="fetching_from_relay">Fetching games from relay</string>
<string name="processing_games">Processing games</string>
<string name="list_item_select">Select</string>
<string name="list_item_deselect">De-select</string>