am c890a4e4: Display dialog if unsaved settings would be lost

* commit 'c890a4e4a2cbb489aea4847cf25368a723586530':
  Display dialog if unsaved settings would be lost
This commit is contained in:
Todd Kennedy 2011-01-14 16:12:03 -08:00 committed by Android Git Automerger
commit 5134b1ee80
5 changed files with 147 additions and 25 deletions

View File

@ -31,6 +31,9 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import java.net.URI;
import java.net.URISyntaxException;
/**
* Common base class for server settings fragments, so they can be more easily manipulated by
* AccountSettingsXL. Provides the following common functionality:
@ -42,18 +45,22 @@ import android.widget.Button;
public abstract class AccountServerBaseFragment extends Fragment
implements AccountCheckSettingsFragment.Callbacks, OnClickListener {
public static Bundle sSetupModeArgs = null;
protected static URI sDefaultUri;
private final static String BUNDLE_KEY_SETTINGS = "AccountServerBaseFragment.settings";
protected Context mContext;
protected Callback mCallback = EmptyCallback.INSTANCE;
protected boolean mSettingsMode;
// The URI that represents this account's currently saved settings
protected URI mLoadedUri;
// This is null in the setup wizard screens, and non-null in AccountSettings mode
public Button mProceedButton;
// This is used to debounce multiple clicks on the proceed button (which does async work)
public boolean mProceedButtonPressed;
public static Bundle sSetupModeArgs = null;
/**
* Callback interface that owning activities must provide
*/
@ -99,6 +106,16 @@ public abstract class AccountServerBaseFragment extends Fragment
return sSetupModeArgs;
}
public AccountServerBaseFragment() {
if (sDefaultUri == null) {
try {
sDefaultUri = new URI("");
} catch (URISyntaxException ignore) {
// ignore; will never happen
}
}
}
/**
* At onCreate time, read the fragment arguments
*/
@ -272,6 +289,21 @@ public abstract class AccountServerBaseFragment extends Fragment
throw new IllegalStateException();
}
/**
* Returns whether or not any settings have changed.
*/
public boolean haveSettingsChanged() {
URI newUri = null;
try {
newUri = getUri();
} catch (URISyntaxException ignore) {
// ignore
}
return (mLoadedUri == null) || !mLoadedUri.equals(newUri);
}
/**
* Save settings after "OK" result from checker. Concrete classes must implement.
* This is called from a worker thread and is allowed to perform DB operations.
@ -288,4 +320,7 @@ public abstract class AccountServerBaseFragment extends Fragment
* Respond to a click of the "Next" button. Concrete classes must implement.
*/
public abstract void onNext();
protected abstract URI getUri() throws URISyntaxException;
}

View File

@ -66,7 +66,6 @@ import java.util.List;
* dealing with accounts being added/deleted and triggering the header reload.
*/
public class AccountSettingsXL extends PreferenceActivity {
// Intent extras for our internal activity launch
/* package */ static final String EXTRA_ACCOUNT_ID = "AccountSettingsXL.account_id";
private static final String EXTRA_ENABLE_DEBUG = "AccountSettingsXL.enable_debug";
@ -243,8 +242,8 @@ public class AccountSettingsXL extends PreferenceActivity {
}
/**
* TODO: Any time we exit via this pathway, and we are showing a server settings fragment,
* we should put up the exit-save-changes dialog. This will work for the following cases:
* Any time we exit via this pathway, and we are showing a server settings fragment,
* we put up the exit-save-changes dialog. This will work for the following cases:
* Cancel button
* Back button
* Up arrow in application icon
@ -254,6 +253,15 @@ public class AccountSettingsXL extends PreferenceActivity {
*/
@Override
public void onBackPressed() {
if (mCurrentFragment instanceof AccountServerBaseFragment) {
boolean changed = ((AccountServerBaseFragment)mCurrentFragment).haveSettingsChanged();
if (changed) {
UnsavedChangesDialogFragment dialogFragment =
UnsavedChangesDialogFragment.newInstanceForBack();
dialogFragment.show(getFragmentManager(), UnsavedChangesDialogFragment.TAG);
return; // Prevent "back" from being handled
}
}
super.onBackPressed();
}
@ -444,12 +452,13 @@ public class AccountSettingsXL extends PreferenceActivity {
public void onHeaderClick(Header header, int position) {
// special case when exiting the server settings fragments
if (mCurrentFragment instanceof AccountServerBaseFragment) {
if (position != mCurrentHeaderPosition) {
boolean changed = ((AccountServerBaseFragment)mCurrentFragment).haveSettingsChanged();
if (changed) {
UnsavedChangesDialogFragment dialogFragment =
UnsavedChangesDialogFragment.newInstance(position);
UnsavedChangesDialogFragment.newInstanceForHeader(position);
dialogFragment.show(getFragmentManager(), UnsavedChangesDialogFragment.TAG);
return;
}
return;
}
// Secret keys: Click 10x to enable debug settings
@ -472,10 +481,23 @@ public class AccountSettingsXL extends PreferenceActivity {
* in {@link #onHeaderClick(Header, int)}. Called after we interrupted a header switch
* with a dialog, and the user OK'd it.
*/
private void forceSwitchHeader(int newPosition) {
mCurrentHeaderPosition = newPosition;
Header header = mGeneratedHeaders.get(newPosition);
switchToHeader(header.fragment, header.fragmentArguments);
private void forceSwitchHeader(int position) {
mCurrentHeaderPosition = position;
// Clear the current fragment; we're navigating away
mCurrentFragment = null;
// Ensure the UI visually shows the correct header selected
setSelection(position);
Header header = mGeneratedHeaders.get(position);
switchToHeader(header);
}
/**
* Forcefully go backward in the stack. This may potentially discard unsaved settings.
*/
private void forceBack() {
// Clear the current fragment; we're navigating away
mCurrentFragment = null;
onBackPressed();
}
/**
@ -539,6 +561,8 @@ public class AccountSettingsXL extends PreferenceActivity {
*/
public void onCheckSettingsComplete(int result, int setupMode) {
if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) {
// Settings checked & saved; clear current fragment
mCurrentFragment = null;
onBackPressed();
}
}
@ -653,27 +677,47 @@ public class AccountSettingsXL extends PreferenceActivity {
/**
* Dialog fragment to show "exit with unsaved changes?" dialog
*/
public static class UnsavedChangesDialogFragment extends DialogFragment {
/* package */ static class UnsavedChangesDialogFragment extends DialogFragment {
private final static String TAG = "UnsavedChangesDialogFragment";
// Argument bundle keys
private final static String BUNDLE_KEY_NEW_HEADER = "UnsavedChangesDialogFragment.Header";
private final static String BUNDLE_KEY_HEADER = "UnsavedChangesDialogFragment.Header";
private final static String BUNDLE_KEY_BACK = "UnsavedChangesDialogFragment.Back";
/**
* Create the dialog with parameters
* Creates a save changes dialog when the user selects a new header
* @param position The new header index to make active if the user accepts the dialog. This
* must be a valid header index although there is no error checking.
*/
public static UnsavedChangesDialogFragment newInstance(int newPosition) {
public static UnsavedChangesDialogFragment newInstanceForHeader(int position) {
UnsavedChangesDialogFragment f = new UnsavedChangesDialogFragment();
Bundle b = new Bundle();
b.putInt(BUNDLE_KEY_NEW_HEADER, newPosition);
b.putInt(BUNDLE_KEY_HEADER, position);
f.setArguments(b);
return f;
}
/**
* Creates a save changes dialog when the user navigates "back".
* {@link AccountSettingsXL#onBackPressed()} defines in which case this may be triggered.
*/
public static UnsavedChangesDialogFragment newInstanceForBack() {
UnsavedChangesDialogFragment f = new UnsavedChangesDialogFragment();
Bundle b = new Bundle();
b.putBoolean(BUNDLE_KEY_BACK, true);
f.setArguments(b);
return f;
}
// Force usage of newInstance()
private UnsavedChangesDialogFragment() {
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final AccountSettingsXL activity = (AccountSettingsXL) getActivity();
final int newPosition = getArguments().getInt(BUNDLE_KEY_NEW_HEADER);
final int position = getArguments().getInt(BUNDLE_KEY_HEADER);
final boolean isBack = getArguments().getBoolean(BUNDLE_KEY_BACK);
return new AlertDialog.Builder(activity)
.setIcon(android.R.drawable.ic_dialog_alert)
@ -683,13 +727,16 @@ public class AccountSettingsXL extends PreferenceActivity {
R.string.okay_action,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
activity.forceSwitchHeader(newPosition);
if (isBack) {
activity.forceBack();
} else {
activity.forceSwitchHeader(position);
}
dismiss();
}
})
.setNegativeButton(
activity.getString(R.string.cancel_action),
null)
activity.getString(R.string.cancel_action), null)
.create();
}
}

View File

@ -262,6 +262,12 @@ public class AccountSetupExchangeFragment extends AccountServerBaseFragment
mTrustCertificatesView.setChecked(trustCertificates);
showTrustCertificates(ssl);
try {
mLoadedUri = getUri();
} catch (URISyntaxException ignore) {
// ignore; should not happen
}
mLoaded = true;
return validateFields();
}
@ -347,7 +353,8 @@ public class AccountSetupExchangeFragment extends AccountServerBaseFragment
* a problem with the user input.
* @return a URI built from the account setup fields
*/
/* package */ URI getUri() throws URISyntaxException {
@Override
protected URI getUri() throws URISyntaxException {
boolean sslRequired = mSslSecurityView.isChecked();
boolean trustCertificates = mTrustCertificatesView.isChecked();
String scheme = (sslRequired)

View File

@ -78,6 +78,8 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment {
private Spinner mDeletePolicyView;
private View mImapPathPrefixSectionView;
private EditText mImapPathPrefixView;
// Delete policy as loaded from the device
private int mLoadedDeletePolicy;
// Support for lifecycle
private boolean mStarted;
@ -333,7 +335,8 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment {
}
if (uri.getScheme().startsWith("pop3")) {
SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, account.getDeletePolicy());
mLoadedDeletePolicy = account.getDeletePolicy();
SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, mLoadedDeletePolicy);
} else if (uri.getScheme().startsWith("imap")) {
if (uri.getPath() != null && uri.getPath().length() > 0) {
mImapPathPrefixView.setText(uri.getPath().substring(1));
@ -363,6 +366,13 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment {
*/
throw new Error(use);
}
try {
mLoadedUri = getUri();
} catch (URISyntaxException ignore) {
// ignore; should not happen
}
mLoaded = true;
validateFields();
}
@ -440,7 +450,8 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment {
* a problem with the user input.
* @return a URI built from the account setup fields
*/
/* package */ URI getUri() throws URISyntaxException {
@Override
protected URI getUri() throws URISyntaxException {
int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
String path = null;
if (mAccountSchemes[securityType].startsWith("imap")) {
@ -485,4 +496,18 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment {
throw new Error(use);
}
}
@Override
public boolean haveSettingsChanged() {
boolean deletePolicyChanged = false;
// Only verify the delete policy if the control is visible (i.e. is a pop3 account)
if (mDeletePolicyView.getVisibility() == View.VISIBLE) {
int newDeletePolicy =
(Integer)((SpinnerOption)mDeletePolicyView.getSelectedItem()).value;
deletePolicyChanged = mLoadedDeletePolicy != newDeletePolicy;
}
return deletePolicyChanged || super.haveSettingsChanged();
}
}

View File

@ -300,6 +300,13 @@ public class AccountSetupOutgoingFragment extends AccountServerBaseFragment
*/
throw new Error(use);
}
try {
mLoadedUri = getUri();
} catch (URISyntaxException ignore) {
// ignore; should not happen
}
mLoaded = true;
validateFields();
}
@ -368,7 +375,8 @@ public class AccountSetupOutgoingFragment extends AccountServerBaseFragment
* a problem with the user input.
* @return a URI built from the account setup fields
*/
/* package */ URI getUri() throws URISyntaxException {
@Override
protected URI getUri() throws URISyntaxException {
int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
String userInfo = null;
if (mRequireLoginView.isChecked()) {